当前位置: 首页 > 后端技术 > Node.js

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

时间:2023-04-03 16:55:06 Node.js

前言基于本人目前在公司的Node微服务实践,在不断维护和升级一个NodeRestfulAPI种子项目,特此分享,供大家参考和讨论。项目中几乎所有内容都使用了node/javascript的最新特性和相应的模块、语法和实践。承接上一篇,本次分享将不分先后顺序详细介绍本项目提供的主要功能。项目github仓库地址,欢迎star:https://github.com/xiaozhongliu/node-api-seed详细项目目录结构.vscodeVSC服务调试/测试调试配置config多环境服务配置,不依赖外部逻辑ctrl控制器,基本对应路由日志服务请求日志,自动生成midwareexpress服务中间件模型数据库模型:mongo、postgres/mysql服务服务层,供控制器/中间件调用测试API测试,运行命令npmtutilvarious工具库,只依赖系统配置.eslintrc.jseslint规则配置app.js应用服务入口文件global-helper.js挂载几个全局助手message.js集中管理接口/系统消息包.json应用服务包配置文件pm2.config.js多环境pm2配置文件router.js集中管理服务路由项目先运行先运行要测试项目,先创建脚本或者执行User.sync()将表结构同步到数据库。服务运行后直接使用postman对提供的接口进行实验:路由注册扩展代码文件:router.js自动判断是否有controller对应的接口数据校验规则集,如果有则使用。包裹控制器以统一捕获抛出的意外错误,app.js中的最后一个中间件将发送警报邮件。提供基本的健康检查接口。接口数据校验代码文件:midware/validate.js&util/validator.js声明和controller按照约定请求时可以校验同名的接口数据校验规则集合。例如:/***validateapi: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的计算规则,参考中间件代码,customer终端要按照同样的规则计算传入,参考postman中的Pre-requestScript:constts=newDate().getTime();constTOKEN="08fbf466b37a924a8b3d3b2e6d190ef3";postman.setGlobalVariable("ts",ts);postman.setGlobalVariable("token",CryptoJS.MD5(TOKEN+ts));结果处理扩展代码文件:util/extender.js为expressresponse添加扩展方法,简化使用。例如://不需要返回数据res.success()//需要返回数据res.success(payload)res.success({accessToken,sysType:getRes.sysType,username:getRes.username,avatar:getRes.avatar,redirectUrl,})接口请求日志代码文件:midware/httplog.js记录请求地址、请求数据、响应数据、响应状态码和处理时间。例如:2018-02-0213:23:46-[B1qkId-Lf]StartPOST/login2018-02-0213:23:46-[B1qkId-Lf]Data{"sysType":1,"username":"unittest","password":"e10adc3949ba59abbe56e057f20f883e"}2018-02-0213: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-0213:23:46-[B1qkId-Lf]完成200(134ms)2018-02-0213:23:49-[SJCJUuZLM]开始GET/verify2018-02-0213:23:49-[SJCJUuZLM]Resp{"code":1,"msg":"success","data":{"username":"unittest"}}2018-02-0213:23:49-[SJCJUuZLM]Done200(7ms)高并发时,请求ID可用于查找同一请求的多行日志记录。响应数据的非侵入式记录是通过在原生res.json方法中添加一个方面来实现的{logger.info(`[${this.reqId}]Resp`,JSON.stringify(json))returnorigin.call(this,json)}支持日志在线预览,可以在浏览器中查看日志文件内容(第一次会有httpauth认证):当然,如果你用ELK(或者ElasticStack),最好一个请求输出一行json,方便logstash或者filebeat抓包。服务监控面板代码文件:midware/monitor.js可以打开这个地址查看服务监控面板(第一次会有httpauth认证):/dashboardJest接口测试代码文件:test/base.test.js有集成VSCJest测试配置,选择JestAllprofile,添加断点按F5开始调试。或者为当前打开的文件选择Jest文件配置文件。刚开始用Jest的时候,只有8000多星,几乎和ava并列第三,现在排在第一,不得不服从自己的眼光,啊哈哈哈哈。。。打嗝。示例:describe('basectrltests',()=>{test('登录成功',async()=>{constdata={sysType:1,username:'unittest',password:'e10adc3949ba59abbe56e057f20f883e'}constres=一个waitclient.POST(`${host}/login`,data)expect(res.code).toBe(1)expect(res.data.username).toBe('unittest')})test('登录失败',async()=>{constdata={sysType:1,username:'unittest',password:'invalidpassword'}constres=awaitclient.POST(`${host}/login`,data)expect(res.code).toBe(message.LoginFail.code)})})执行npmt,测试结果如下:接口示例说明提供了3个基于jsonwebtoken(jwt)的接口示例:注册、登录、验证。验证接口仅供参考,实际使用时要在中间件中验证jwt,此类中间件类似:module.exports=async(req,res,next)=>{if(!['/path/needs/jwt/verification'//TODO:考虑把它放在配置中].includes(req.path)){returnnext()}////测试生成一个jwt令牌//constjwtToken=awaitjwtSvc.sign({//foo:'bar'//})//console.log(jwtToken)//验证const{authorization}=req.headersif(!authorization){returnnext(newError('verifyfail'))//TODO:修改错误处理,下同}constjwtToken=authorization.substr(7)letpayloadtry{payload=awaitjwtSvc.verify(jwtToken)}catch(e){returnnext(newError('verifyfail'))}if(!payload){returnnext(newError('verifyfail'))}console.log(payload)//TODO:设置为req,即可获取next()后来}thunk函数打包代码文件:service/*.js节点发展到今天,使用原生async/await进行代码异步流程控制已经很久了。很多库都提供了基于promise的API,但是不可避免地有很多基于thunk的库,或者一个不完整的库同时提供了promiseAPI。对于thunk函数,我们可以使用node提供的util.promisify将其包装为一个promise。例如:/***设置一个哈希字段的值*@param{string}keyhashkey*@param{string}field字段名*@param{string}value字段值*/asynchset(key,field,value){if(typeofvalue==='object'){value=JSON.stringify(value)}returnpromisify(redis.hset)(key,field,value)},/***获取哈希字段的值*@param{string}键hashkey*@param{string}field字段名*/asynchget(key,field){constvalue=awaitpromisify(redis.hget)(key,field)try{returnJSON.parse(value)}catch(e){返回值}},