Node API 经验与种子项目分享 (二)功能详解 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
liux466713
V2EX    Node.js

Node API 经验与种子项目分享 (二)功能详解

  •  1
     
  •   liux466713 2018-02-04 23:58:56 +08:00 4898 次点击
    这是一个创建于 2829 天前的主题,其中的信息可能已经有所发展或是发生改变。

    前言

    基于本人在现在公司的 Node 微服务实践, 不断维护升级着一个 Node Restful API 种子项目, 特此共享出来以供借鉴和讨论. 项目中几乎所有的东西都使用了 node/Javascript 及相应模块的最新功能, 语法, 和实践.

    上一篇帖子, 本次分享将会对此项目提供的各个主要功能不分先后做下详细介绍.
    项目 github 仓库地址, 欢迎 star: https://github.com/xiaozhongliu/node-api-seed

    详解

    项目目录结构

     .vscode VSC 服务调试 /测试调试配置 config 多环境服务配置, 不依赖外部逻辑 ctrl 控制器, 基本与路由对应 log 服务请求日志, 自动生成 midware express 服务中间件 model 数据库模型: mongo, postgres/mysql service 服务层, 供控制器 /中间件调用 test API 测试, 运行命令 npm t util 各种工具库, 仅依赖 系统配置 .eslintrc.js eslint 规则配置 app.js 应用服务入口文件 global-helper.js 挂载少许全局 helper message.js 集中管理接口 /系统消息 package.json 应用服务包配置文件 pm2.config.js 多环境 pm2 配置文件 router.js 集中管理服务路由 

    项目首次运行

    首次运行项目进行测试, 先脚本建表或执行User.sync()将表结构同步到数据库.
    服务运行起来之后, 直接使用 postman 来实验提供的接口:
    Run in Postman

    路由 注册扩展

    代码文件: router.js
    自动判断有没有控制器对应的接口数据校验规则集合, 如有则采用.
    包装控制器来统一捕捉抛出的非预期错误, 并将在 app.js 中最后一个中间件发送告警邮件.
    提供基础健康检查接口.

    接口数据校验

    代码文件: midware/validate.js & util/validator.js
    按约定声明与控制器名称相同的接口数据校验规则集合, 即可在请求时进行验证. 例如:

    /** * validate api: login */ login: [ // 参数名 参数类型 是否必传 ['sysType', Type.Number, true], ['username', Type.String, true], ['password', Type.String, true], ], 

    类型校验方法大多是 express-validator 模块提供的, 可以 自定义类型及其校验方法. 例如:

    isHash(value) { return /^[a-f0-9]{32}$/i.test(value) }, isUnixStamp(value) { return /^[0-9]{10}$/.test(value) }, 

    无效请求过滤

    代码文件: midware/auth.js
    此中间件做的无效请求过滤, 和认证没关系. 具体通过 header 中传来的 ts 和 token 校验请求有效性.
    ts 或 token 未传则会直接回绝请求, 这个可以过滤掉 95%以上的无效请求了.
    ts 和 token 对校验失败回绝请求, 不会执行后续业务逻辑. ts 和 token 的计算规则参考中间件代码, 客户端要以相同的规则计算后传入, 参考 postman 中 Pre-request Script:

    const ts = new Date().getTime(); const TOKEN = "08fbf466b37a924a8b3d3b2e6d190ef3"; postman.setGlobalVariable("ts", ts); postman.setGlobalVariable("token", CryptoJS.MD5(TOKEN+ts)); 

    结果处理扩展

    代码文件: util/extender.js
    给 express 的 response 添加扩展方法, 简化使用. 例如:

    // 无需返回数据 res.success() // 需要返回数据 res.success(payload) res.success({ accessToken, sysType: getRes.sysType, username: getRes.username, avatar: getRes.avatar, redirectUrl, }) 

    接口请求日志

    代码文件: midware/httplog.js
    记录请求地址, 请求数据, 响应数据, 响应状态码及处理时长. 例如:

    2018-02-02 13:23:46 - [B1qkId-Lf] Start POST /login 2018-02-02 13:23:46 - [B1qkId-Lf] Data {"sysType":1,"username":"unittest","password":"e10adc3949ba59abbe56e057f20f883e"} 2018-02-02 13:23:46 - [B1qkId-Lf] Resp {"code":1,"msg":"success","data":{"accessToken":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VybmFtZSI6InVuaXR0ZXN0IiwiaWF0IjoxNTE3NTQ5MDI2LCJleHAiOjE1MTg0MTMwMjZ9.-U4P6ksOUN6WsmI3ZEWow9npYDmO-QI020eVY5Mg2bQ","sysType":1,"username":"unittest","avatar":"https://nodejs.org/static/images/logo.svg"}} 2018-02-02 13:23:46 - [B1qkId-Lf] Done 200 (134ms) 2018-02-02 13:23:49 - [SJCJUuZLM] Start GET /verify 2018-02-02 13:23:49 - [SJCJUuZLM] Resp {"code":1,"msg":"success","data":{"username":"unittest"}} 2018-02-02 13:23:49 - [SJCJUuZLM] Done 200 (7ms) 

    高并发时可通过请求 ID 来找到同一次请求的多行日志记录.
    通过给原生 res.json 方法增加一个切面来实现非侵入记录响应数据:

    // add a logging aspect to the primary res.json function const origin = express.response.json express.response.json = function (json) { logger.info(`[${this.reqId}] Resp `, JSON.stringify(json)) return origin.call(this, json) } 

    支持日志在线预览, 可在浏览器查看日志文件内容(首次会有 http auth 认证):

    当然如果使用的 ELK(或者 Elastic Stack), 则对于一次请求最好就输出一行 json, 以方便 logstash 或者 filebeat 抓取.

    服务监控面板

    代码文件: midware/monitor.js
    可以打开这个地址查看服务监控面板(首次会有 http auth 认证): /dashboard

    Jest 接口测试

    代码文件: test/base.test.js
    已经集成 VSC Jest 测试配置, 选择 Jest All 这个 profile, 加断点并 F5 即可开始调试. 或者对当前打开的文件选择 Jest File 这个 profile.
    我开始用 Jest 的时候它才 8000 多 star, 和 ava 差不多并列第三, 但现在已经排第一了, 不得不服自己的眼光, 啊哈哈哈哈...嗝. 样例:

    describe('base ctrl tests', () => { test('login succeeds ', async () => { const data = { sysType: 1, username: 'unittest', password: 'e10adc3949ba59abbe56e057f20f883e' } const res = await client.POST(`${host}/login`, data) expect(res.code).toBe(1) expect(res.data.username).toBe('unittest') }) test('login fails ', async () => { const data = { sysType: 1, username: 'unittest', password: 'invalid password' } const res = await client.POST(`${host}/login`, data) expect(res.code).toBe(message.LoginFail.code) }) }) 

    执行 npm t, 测试结果如下:

    接口示例说明

    提供了 3 个基于 jsonwebtoken (jwt) 的接口示例: 注册, 登录, 验证.
    验证接口仅供参考, 实际使用时应在中间件中验证 jwt, 这样的中间件类似:

    module.exports = async (req, res, next) => { if ( ![ '/path/needs/jwt/verification' // TODO: 考虑放到配置 ].includes(req.path) ) { return next() } // // test generating a jwt token // const jwtToken = await jwtSvc.sign({ // foo: 'bar' // }) // console.log(jwtToken) // verify const { authorization } = req.headers if (!authorization) { return next(new Error('verify fail')) // TODO: 修改错误处理, 下同 } const jwtToken = authorization.substr(7) let payload try { payload = await jwtSvc.verify(jwtToken) } catch (e) { return next(new Error('verify fail')) } if (!payload) { return next(new Error('verify fail')) } console.log(payload) // TODO: 设置到 req 上, 后续就能拿到 next() } 

    thunk 函数包装

    代码文件: service/*.js
    node 进化到今天, 用原生 async/await 做代码异步流程控制也已经好久了. 很多库提供了基于 promise 的 API, 但难免还有很多基于 thunk 的库, 或者同时提供了 promise 的 API 但还不完善的库.
    对于 thunk 函数我们可以使用 node 提供的 util.promisify 来包装为 promise. 例如:

     /** * set value of a hash field * @param {string} key hash key * @param {string} field field name * @param {string} value field value */ async hset(key, field, value) { if (typeof value === 'object') { value = JSON.stringify(value) } return promisify(redis.hset)(key, field, value) }, /** * get value of a hash field * @param {string} key hash key * @param {string} field field name */ async hget(key, field) { const value = await promisify(redis.hget)(key, field) try { return JSON.parse(value) } catch (e) { return value } }, 
    4 条回复    2018-02-09 13:58:02 +08:00
    liux466713
        1
    liux466713  
    OP
       2018-02-05 16:23:24 +08:00   1
    默默 star 又不来这里支持一下是什么鬼, 试试能不能顶上去
    wisetc
        2
    wisetc  
       2018-02-09 12:59:04 +08:00
    支持一下,希望能够顶上去
    strugglexiang
        3
    strugglexiang  
       2018-02-09 13:43:55 +08:00
    默默 star,不发言,说的就是我吗
    liux466713
        4
    liux466713  
    OP
       2018-02-09 13:58:02 +08:00
    不晓得 V2EX 的帖子排序规则, 貌似上不去啊, node 在 V2EX 确实是很尴尬, 很冷清啊
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     1163 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 34ms UTC 17:42 PVG 01:42 LAX 09:42 JFK 12:42
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86