夜已深, 愚人节有没有对中意的女生表白啊哈哈
小编在这里先祝福大家
这个周末, 本来想把最近还没有整理的几篇文章这里好发给大家的无奈小编周末有点晕, 还没整理好可能是周六晚上烤串吃多了
最近有一篇是关于 module(模块化) & babel 的文章不过还没整理出来那先来一篇 node.js 热热身这篇文章里面的很多东西, 现在富途前端还是在用, 对后面大家去理解富途前端因该会有帮助
有个好消息是, 富途的前端框架已经慢慢的被 vue 替代了 (原来使用的是 JQ , angular.js) 接入层 node.js 也在推进中, 对外的服务指日可待相信喜欢 vue.js ,node.js 的小伙伴已经蠢蠢欲动了
没错, 小编就是富途 node.js 服务推动者之一
在富途做前端, 不仅需要写前端, 还需要写 node.js 服务的, 这个大家不用想太多
那还是回归正题, 作为第一个吃螃蟹的小编, 是如何在工作中学习运用 node.js 的把
注意: 现在富途的前端框架已经往 vue.js 迁移但不会影响大家理解阅读
原文章来源于富途 web 博客: 原文链接
正文
一次偶然的机会让我有幸跨越浏览器的鸿沟来真真切切的体验一次 Node.js
首先, 我想说: 很荣幸在经历了 2 个月的努力, 第一个 Node.js 项目落地了整个项目做下来, 还是算比较顺畅的
事情很简单: Node.js 做的是接入层
事出有因
前端的技术革新是日新月异的, 前端工程化已经离不开 Node.js 现在大多数的项目使用的是前后端分离的架构, 后端提供接口前端通过接口数据进行数据渲染但是现在前端的代码逻辑越来越复杂, 场景也越来越多这套架构是否适合所有的应用场景值得考虑了大前端的出现, 就是一种尝试吧试图通过 Node.js 接入来应对各种应用场景
不管是个人还是团队, 技术革新是必须的现在我们团队面临的问题就是如此, 所以必须有人迈出这一步而我也很幸运的成为第一个吃螃蟹的人
始作俑者
不管什么技术, 不管怎样的优秀, 它的运用与否都是要经过慎重考虑的但, 总不能都不用吧那怎么办呢找项目试点呗, 线上项目运行的好好的肯定不能重构, 而且人力紧张啊只能找新项目了刚巧, 公司需要做新的项目, 本以为按老路子前后分离做可突然有一天...
组长说: 团队不是要进行技术选型吗? 看这个项目使用 Node.js 做接入层可不可行?
经过慎重考虑, 我回答说: 可以没问题(管他 3721, 应了再说)
借我老大的一句话: 技术这东西不落地, 说了也白说
背景: 其实团队对 Node.js 一直都保持着高度的关注, 包括我之前我一直都有在对 Node.js 的源码进行解读和研究基础架构组也一直在进行 Node.js 技术框架进行调研, 希望打造一套适用于团队开发的集成项目框架
所以我相信: 机会总是会照顾有准备的人的
就这样我的 Node.js 之旅就开始了
万事开头难
虽然我平时可能天天都会用 Node.js 跑命令, 写各种 npm 包, 甚至还写过一些自己的项目但是要真正的用 Node.js 来真正开发项目还是有压力的因为这种项目技术架构下要求我操心的东西变多了平时的时候可能我只要写一些前端逻辑代码, 做做前端工程化但是这种架构下, 要求我必须去学习和应用我不熟悉的东西
我大致列了一些大的方向:
1.Node.js 接入层的总体架构是怎样的?
2. 前端技术用什么?
3. 前端工程化如何做?
4. 项目如何根据不同的环境 (常有的环境: 开发, 测试, 正式) 运行?
5. 前端自动化怎么搞?
6. 单元测试?
7. 编码风格?
8.Node.js 如何和服务端对接?
9. 日志, 上报, 登录服务接入, 权限校验等等我应该怎么做?
10. 项目如何发布上线?
11. 上线了如何保证服务稳定?
12. 如何 debug 问题?
可能还有很多很多需要处理的问题但是这已经可以看出一下端倪了瞬间感觉我懂的只有冰山一角代码码的再漂亮感觉也无力要求的不再是单一的编码能力, 而是大局观, 思维角度的转变
但不管怎样, 新建 git 仓库开始搞呗
如何得到一个合适的项目架构
这个确实是个问题, 架构设计的合不合理会影响到后期编码是否可以做到快速开发, 还会影响后期的功能迭代和维护
那么问题来了, 我是预先设计还是预先编码?
这里我选择了先编码, 然后重构
背景: 因为上文已经说过, 基础架构组已经有一个简单的 Node.js 集成框架, 它是不完整的, 但是它够简单也就是说我在这上面重构出自己的项目架构是完全没有问题的
你可能会觉得还是要预先设计啊?
说的是侧重点不一样, 侧重于编码实现, 将这个项目跑起来, 然后通过重构去寻找出合适的项目架构
对于先编码还是设计这个问题我借用重构里面的是一句话:
重构改变了预先设计的角色如果没有重构, 你就必须保证预先做出的设计是正确无误, 这压力太大了这意味着如果将来需要对原始设计做任何修改, 代价都将非常昂贵因此你需要把更多的精力放在预先设计上, 以避免日后的修改如果选择重构, 问题的重点就转变了你任然做预先设计, 但是不必一定要找出证正确的解决方案, 此刻的你只需要得到一个合理的解决方案就够了 -- 摘自重构 - 改善既有代码的设计
把一个简单的解决方法重构成一个灵活的解决方法有多难? 答案是: 相当容易 -- 摘自重构 - 改善既有代码的设计
实在不明白我推荐你去看看重构 - 改善既有代码的设计这本书
所以我将侧重点放在了预先编码上, 让后在整个项目 demo 跑起来之后再去寻找合适的架构一个合理的架构体系就是把代码放到它应该出现的位置上去代码是具有流失性的, 就好比一个房间从来不整理的话, 就会变的脏乱不堪重构就是将代码再次整理将它放回原位
技术框架选型考虑
技术框架的选择会影响着项目的总体架构, 编码, 产出效益, 以及后期人员维护的成本
首先我想说: 不管前端还是后端用什么框架我觉得还是要站在团队的角度上去考虑这个问题, 毕竟这不是个人的项目总不能说我不在就没人能维护这个项目吧
Node.js 后端
koa2 为什么没有使用 koa 或者 express 等框架, 或者为什么团队不自己开发
Node.js v8LTS 已经快要来临 koa 已经升级到了 koa2 版本, 没有必要再用旧的 express 太老了 koa2 在这两年已经锋芒毕露, 现阶段团队没有必要花费很多的人力去搞一套自己的框架, 可以转变思维在 koa2 的基础上做一个集成的适合团队项目使用的框架
基于这个基础架构团队使用 koa2 作为主框架使用在现阶段是最合适的特别是在 Node.js v7.6+ 原生支持了 async 和 await 语法
前端框架
jQuery 的王朝已经渐渐被瓦解 angular.js,react 和 vue 三足鼎立的时代已经到来再次基于团队的现状, 选择了最有优势的 angular.js v1.x
在这里我并没有说其他框架不好的意思, 完全是基于团队现状的考虑, 以及当前框架是否可以帮助我高效的完成开发的一种考虑假如有一天我觉得 angular.js 已经不适合现阶段项目开发需求, 我会义不容辞的提出我的疑问
比如: 项目需要我们考虑加速页面渲染时, 要考虑服务器渲染; 服务器压力山大时, 考虑前后端分离同构作为最合适的编码方式 react 和 vue 都是不错的选择
框架没有对与错, 只有合不合适
webpack2 作为当红炸子鸡, 我也是优先考虑的至于为什么没有选 webpack3 嘛
其实是这样的, 我也有实际的去使用 webpack3 来做过测试, 就是这个项目我的衡量标准就是压缩要比现在的要小最后没有达到预期效果所以没有进行合并
gulp 工作流处理, 没毛病这里可能会有的让人疑惑, 为什么使用了 webpack2 还要使用 gulp? 为什么 2 个都要用?
其实对于这 2 个组件, 它们没有绝对的对立关系在这里它们是相辅相成的
总的前端框架: angular.js v1.x + webpack2 + gulp
babel 用来编译前端代码
项目使用的主要框架, 如图:
前端工程化
项目的总体架构和前端技术框架的选型势必会对前端工程化产生深远的影响前端代码放到哪里, webpack 打包如何做, 产出文件放到哪里 gulp 需要做哪些事情, 多还是少, 烦不烦琐这种种问题都会对你项目的架构做出挑战这也就是我为什么先编码然后通过重构来调整项目架构的原因之一假如你预先就把项目的总体架构规定死了, 那么后期你的编码就会想尽办法的去套这个项目架构, 写出来的代码可想而知一定是不尽人意的
那么第一个问题就来了
自己编写的 anglaur.js 部分的源码放到哪里
对于这个问题, 在使用 Node.js 开发初期, 我就对基础的架构做出了建议: 前端源码不能放到服务器静态资源目录只有打包后的文件才会放到静态资源文件目录, 除非该文件可以直访问
这就意味着, 我需要寻找一个文件目录来放置前端源代码最合理的位置就是于服务器目录平级放置
webpack
通过 webpack 的编译打包, 将文件保存到静态资源目录我这里把所以和代码相关的打包和编译任务都交给了 webpack, 其中还包含公共文件的提取, 版本控制, 压缩, 以及模版文件注入
如何进行版本控制
版本控制用的比较多的就 2 种: 基于文件和基于 hash
基于文件就好比, 每次打包的时候都会生成不同文件名的文件有利于在线上跑多个版本的功能
基于 hash 就意味着线上这个功能的文件永远就只有一个, 无法进行全量灰度
这里有个问题就是: 基于文件的版本控制, 难点就在于打包后的. js 或. CSS 文件名是不可控的, 所以, 并不能把引入的 js 或 css 文件路径写死在 html 模版文件里面所以通过 webpack 打包的时候, 我需要指定模版文件是哪一个, 通过 webpack 的模版文件注入插件完成 js 或 css 文件路径的引入
其它方式; 通过在 webpack 打包完成之后, 将返回值种的 hash 参数保存下来这样也可以完成基于文件的版本控制
gulp 的工作流
gulp 结合 webpack 的应用如鱼得水, webpack 打包任务是 gulp 任务流里最重要的一环考虑到打包编译, 都交给 webpack 做了那 gulp 所要做的就是保证前端各个任务正确的执行包括何时执行 webpack 打包, 完成打包以后做什么
前端自动化
这里的自动化可能与你在别的地方所说的自动化可能有分歧这里的前端自动化主要指的是在前端代码如何完成自动化打包编译其实项目中可以进行自动化的流程有很多, 我在项目里接入的是 jenkins, 主要用来自动完成前端打包编译, 然后通过 zip 命令对 webpack 打包编译后的所有文件进行打包成. zip 文件因为打包后的文件不入库
这里有疑惑是正常的首先为什么不把 weboack 打包后生成的文件纳入 git 版本库?
道理很简单, git 版本库里面的任意一个文件产生变化, 就会有下一个版本号产生 webpack 每次打包编译就势必会产生文件变化, 如果把打包文件纳入版本库就必须提交文件, 从而产生版本号也就是说我本地提交一次代码到 git 库后, jenkins 会进行打包, 然后打包文件又必须提交回 git 库, 这样就相当于每次提交代码否会产生 2 次提交记录 (一次我自己的提交, 一次 jenkins 完成自动化打包后的提交) 所以为了不让 jenkins 完成打包后向 git 代码库提交文件, 所要做的就是把 webpack 打包后产生的文件都移除版本库
但问题没有这么简单, webpack 打包不纳入版本库, 发布的时候, 这些 webpack 打包后产生文件怎么发布这里解决方案就是通过把所有和 webpack 打包相关的文件用 zip 命令打包成一个 ${commitId}.zip 包(commitId 是 git 每次提交参数的可以通过 bash 获取:
commitId=$(git rev-parse HEAD)
)这样发布的时候就可以通过 commitId 找到 ${commitId}.zip 这个压缩包, 然后解压它到指定位置即可
为什么有 2 个打包任务?
第一次是 webpack 打包, 前端代码需要打包编译第二次是文件打包, 发布需要, 原因很就是 webpack 打包文件不入库的解决方法
所以要求团队中必须会搭建并且有使用过 jenkins, 这个工具对团队的帮助是非常大的, 预先打包文件并缓存, 比在发布项目的时候再进行打包要好很多可以预先发现打包问题及时进行补救, 以免发布时打包出现问题而影响发布进度和线上项目的正常运行
git 仓库支持添加 hooks 所以可以在 git 库里添加触发事件让 jenkins 自动完成打包
假如有一天, 我需要写单元测试的时候, 也可以试着让 jenkins 帮我跑自动化测试了这算是我回答了单元测试的问题吗? 哈哈哈哈哈哈哈
前端问题基本解决了, 现在问题抛到了服务端
Node.js 服务端运行环境配置
写个项目, 要跑起来很简单, 我的项目入口文件是 server/index.js 通过执行如下命令就可以启动:
node server/index.js
但有时候, 环境并没有我想的那么简单因为项目需要针对不同的环境运行, 所以必需对不同的运行环境使用不同的配置文件这样就需要我在启动 Node.js 服务的时候, 必须携带不同的参数所以要求我在编码的时候尽可能的做到环境参数的配置化牵涉到与执行环境有关的参数尽量进行配置化
Node.js 接入层服务的接入, 权限的校验
其实对于一个小白来说, 很担心的是我如何才能在 Node.js 里面往真正的服务器发起 request 请求我项目站点的登录服务鉴权如何去做, 以及用户登录了, 有没有权限去访问都是个问题
http 服务的接入
通过 http 模块发起 requset 请求其实开始的时候我也是一脸茫然的, 如何在接入层请求后端服务, 可想而知这是之前作为前端的我从来没有考虑过的现在回想起来就那么回事有些事情想着可能很复杂, 真正的做起来就好像有种:
山重水复疑无路, 柳暗花明又一春
的感觉
Node.js 接入层请求后端服务简单的代码实现:
- exports.example = async (ctx)=>{
- let options = {
- port: 80,
- hostname: 'www.test.com',
- method:'GET',
- path:'/api/getuser?token=document.cookie.token'
- };
- let getData = function (){
- return new Promise((resolve , reject)=>{
- let request = http.request(options , (socket)=>{
- let data = '';
- console.log('status:' , socket.statusCode , socket.headers);
- socket.on('data' , (chunk)=>{
- data += chunk;
- });
- socket.on('end' , ()=>{
- console.log('server call back get data:' , data);
- return resolve(data);
- });
- socket.on('error' , (e)=>{
- return reject(data);
- });
- });
- request.end();
- });
- }
- ctx.body = await getData();
- }
这里我没有考虑 https 的方式, 因为 https 是建立在 SSL/TLS 之上的, 也就是说, 需要有私钥和公钥和 CA 证书才行 CA 证书虽说可以自己颁发但还是得本机自行安装才有效对 https 自己颁发 CA 证书感兴趣的可以看看这篇文章: HTTPS 自签发 CA 证书
后端服务器 (PHP/JAVA...) 需要做的就是根据请求参数是否合法已经齐全, 然后验证调用者是否有权限使用该功能这样的案例比比皆是, 比如使用第三方服务
小到 Number 校验
有可能最简单的参数校验都不知道如何校验这跟 javascript 语言以及前端的思维方式有关我开始的时候也是这样, 感觉写起代码来怪怪的
其实这是一个简单的例子, 在前端检验一个 Number 类型的值是不是有效, 我一般是通过:
num = typeof num === 'number' && num === num && num !== Infinity ? num : 0;
这种思路和逻辑放在前端完全是没有问题的, 但是在 Node.js 接入层这么写感觉很尴尬所以要转变我的思维方式:
num = Number.isFinite(num) ? num : 0;
小到参数的校验, 我都要认真的考虑是时候改变自己的思维方式了, 考虑使用 JavaScript 原生的方式处理会比自己写好很多
权限的校验
我并不希望所有的用户都能访问这个项目, 即使他已经登录了也不行这就是我要解决的问题
权限管理在这里就显得极其重要了最好的方式就是把权限相关的功能进行服务化
使命感觉才刚刚开始!!!!!
项目的部署上线
可以说我对项目部署和运维基本上是没有经验但是有一点就是项目上线后的可用率是必须要保证的不能因为一点小问题, 就让服务挂掉, 然后还要人屁颠屁颠的重新手动重启吧也不能说服务器断电了, 重启后也要手动启动吧这一些列的问题都是必须解决的
pm2
很高效的开发完成了项目后, 其实项目的真正使命才要刚刚开始, 如何保证服务在线上稳定的运行, 保证高可用率这就需要借助其它组件来完成了使用 pm2 管理确实是个好的方案
首先通过 npm install -g pm2 进行安装
安装完成了之后, 就可以在项目中进行 pm2 相关配置
案例:
- //test.config.js
- 'use strict';
- //pm2 配置文件
- module.exports = {
- apps: [{
- name: 'test',
- script: './server/index.js',
- // 应用入口
- cwd: './',
- instances: 1,
- watch: ['server'],
- env: {
- 'NODE_ENV': 'development',
- },
- env_production: {
- 'NODE_ENV': 'production',
- },
- exec_mode: 'cluster',
- source_map_support: true,
- max_memory_restart: '1G',
- // 日志地址
- error_file: '/data/logs/pm2/test_error.log',
- out_file: '/data/logs/pm2/test_access.log',
- listen_timeout: 8000,
- kill_timeout: 2000,
- restart_delay: 10000,
- // 异常情况
- max_restarts: 10
- }]
- };
然后就可以通过命令启动:
- pm2 start test.config.js
- nginx
Nginx 是俄罗斯人编写的十分轻量级的 HTTP 服务器, Nginx, 它的发音为 engine X, 是一个高性能的 HTTP 和反向代理服务器 nginx 配置也是必不可少的,
80
端口就一个, 所以我需要 nginx 进行转发
例如下面的案例:
- upstream test_upstream {
- server 127.0.0.1:6666;
- keepalive 64;
- }
- server{
- listen 80;
- server_name www.test.com;
- client_max_body_size 10M;
- index index.html index.htm;
- error_log /data/nginx/log/error_www.test.com.log;
- access_log /data/nginx/log/access_www.test.com.log combined;
- location / {
- proxy_store off;
- proxy_redirect off;
- proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
- proxy_set_header X-Real-IP $remote_addr;
- proxy_set_header Host $http_host;
- proxy_set_header X-Forwarded-Proto $scheme;
- proxy_set_header Remote-Host $remote_addr;
- proxy_set_header X-Nginx-Proxy true;
- proxy_set_header Upgrade $http_upgrade;
- proxy_set_header Connection "upgrade";
- proxy_http_version 1.1;
- proxy_pass http://test_upstream/;
- proxy_read_timeout 60s;
- }
- }
项目启动的端口是本机的
6666
端口, 但是我不可能说访问 www.test.com 的时候后面还带着端口号吧这个时候就是 nginx 发挥作用的时候, 访问域名不带端口默认使用 80 端口, 由 nginx 做反向代理到我服务
6666
端口
这里有一点 post 请求时
client_max_body_size
参数的设定直接会影响 data 的大小
日志, 上报, 运营维护
项目的健康与否, 都会在日志和上报中体现我只需要每天看看日志, 看看视图就可以对当天项目的运行情况做一个大致的了解如果没有这些辅助的功能, 两眼一抹黑, 发生啥事都不知道
编码风格
编码风格方面遵循 eslint 的语法标准使用了最新的 async/await 和 import 语法
debug 代码
Node.js 已经支持在 chrome 中直接调试 Node.js 代码, 只要在启动项目的时候添加 --inspact 参数
node --inspect server/index.js
复制上面红框的 url 链接到 chrome 里面打开, 然后点击 start 后, 再访问页面, 需要暂停的时候可以点击 stop, 进行代码分析
总结
作为一个初学者, 我只能说 Node.js 在做接入层上, 确实是可以做到如鱼得水, 关键点就是契机抛开 Node.js 接入层, 前端的工程化是完全可以做的但是服务器同构渲染是没有办法做到的, 除非与后端同学配合; 使用 Node.js 接入层, 那么前端在处理一些棘手的问题时就会游刃有余, 而且后端服务会得到更深一层的保护, 不至于说后端服务直面攻击, 因为多了一层 Node.js 接入层在前面
来源: https://juejin.im/post/5ac0fc51f265da23970701b9