# node_project_standard **Repository Path**: alloyzy/node_project_standard ## Basic Information - **Project Name**: node_project_standard - **Description**: node(express)项目基本结构 - **Primary Language**: JavaScript - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2021-04-02 - **Last Updated**: 2023-12-13 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ### 😎说在前面: ##### 简单的说 Node.js 就是运行在服务端的 JavaScript。Node.js是一个事件驱动I/O服务端JavaScript环境,基于Google的V8引擎,V8引擎执行Javascript的速度非常快,性能非常好。 `node 擅长的是 IO 而不是 CPU 计算`。 ###😂Node.js的框架列举: ##### Express(官网、Github、NPM)TJ大神开发,Node.js官方推荐 ##### hapi(官网、Github、NPM) ##### koa.js(官网、Github、NPM) ##### egg.js(官网、Github、NPM) ###👉框架大同小异,这里我以我用的`express`作为举例👈 ###一、目录说明 具体见:http://gitlab.mvmtv.cn/node/search.mvmtv.com/tree/demo ``` |– app.js 服务的入口文件 |– api 具体接口的业务逻辑目录(可以按照功能模块分多个js文件) |  |– user.js (举例,用户相关的接口) |  |– ... 可以有一个或者多个 |– config 各种配置文件的目录(根据实际项目情况而定) |  |– baseApi.js 调用的其他接口基础路由配置 |  |– db.js 数据库连接配置 |  |– globalCode.js 全局返回状态码以及描述文件 |  |– process.json 服务的启动配置文件 |– jobs 队列(定时)任务(agenda.js 或者 node-schedule) |  |– sendEmail.js 比如每天的凌晨0点0分0秒给用户发送邮件 |– loaders 将启动过程模块化 |  |– index.js 初始化各个模块入口文件 |  |– express.js express初始化 |  |– mysql.js mysql初始化 |  |– router.js 路由初始化 |– models 数据库模型 |  |– user.js 用户相关的数据库操作【全项目所有有关数据库的操作都只能在数据库模型(models)下存在】 |– middleware 中间件目录(存在于路由和业务代码之间的逻辑,或者一些公用的方法等根据实际项目情况而定) |  |– verify.js 鉴权方法文件 |  |– request.js 通用请求方法 |  |– setLog.js 自定义日志记录文件 |– node_modules 依赖模块 |  |– 各种依赖模块 |– router 路由定义目录 |  |– routerIndex.js 路由统一入口文件 |  |– routerList.js 路由列表文件 |– services 存放所有商业逻辑 |  |– mailer.js 用户发送邮件方法 |– package-lock.json 明确的各依赖版本号,实际安装的结构(自动生成) ``` express官方文档 : https://www.expressjs.com.cn/ ###二、开发说明🤪 ####1、路由定义 路由可根据实际情况`语义化`定义,可以有一级或多级路由 eg: /article /article/del(文章的删除) /article/update(文章的更新) 路由定义在 router => routerList.js 文件里,`原则是路由定义里不掺杂任何业务代码🤝`,路由列表清晰明了,举例如下: ``` const routerList = [ commRouterSet("getUserInfo", getUserInfo), //获取用户信息 commRouterSet("testApi", testApi), //示例接口 ] ``` ####2.目录层次说明✍️ #####2.1 app.js: ``` 项目的入口文件 ``` #####2.2 api目录: ``` 具体的业务逻辑代码 ``` #####2.3 config目录: ``` 配置文件存放目录 ``` #####2.4 jobs目录: ``` 队列(定时)任务 【个人角度上来说并不建议使用 node完成定时任务,毕竟 node 擅长的是 IO 而不是 CPU 计算】 ``` #####2.5 loaders目录: ``` 将启动过程模块化,将项目用到的模块,依赖全部模块化,拆分启动加载过程到独立模块中 ``` #####2.6 models 数据库模型目录: ``` 整个项目数据库的操作模块,除了该模块以外,其他模块不应该有任何形式的数据库操作 ``` #####2.7 middleware目录: ``` 中间件存放目录,比如一些鉴权,一些日志搜集等或者其他一些自定义的公用方法 ``` #####2.8 services目录: ``` 这里的代码,不应该有任何形式的数据操作,不应包含 request 和 response,不应返回任何与传输层关联的数据,如状态码和响应头 ``` ####项目结构总结:👇 a.关注点分离原则,把业务逻辑从路由中分离出去🥪 ``` //路由列表 const routerList = [ commRouterSet("getUserInfo", getUserInfo), //获取用户信息 commRouterSet("testApi", testApi), //示例接口 ] ``` `路由文件中只定义路由,不掺杂任何业务代码,业务代码统一放在api目录下` b.同理,不要在控制器中直接处理业务逻辑☠️ c.使用服务层(service)来处理商业逻辑💼 ``` module.exports = sendMail = function () { // 发送邮件 transporter.sendMail(mailOptions, function (error, info) { if (error) { return console.log(error); } }); } ``` `上述demo中services下的mailer就是一个邮件发送方法,他和业务逻辑不同,也不依赖任何业务逻辑,不受业务逻辑影响` d.使用发布 / 订阅模式🎙️ e.模块功能分离,按需使用 ``` async function login() { let userInfo = await userModel(uid); let sendEial = await sendMail(); res.send({ return: globalCode.SUCCESS, data: userInfo }); } ``` `如上的登录接口示例,同步的调用依赖服务,分别有获取用户信息方法model userModel方法,发送邮件方法 sendMail` f.使用loaders初始化各配置服务 ``` module.exports = async (app) => { //初始化express模块 await expressLoader(); console.log('Express Intialized'); //初始化路由模块 await routerLoader(app); console.log('Router Intialized'); //初始化mysql模块 await mysqlLoader(); console.log('Mysql Connection'); //发送邮件定时任务 await sendEmailJobs(); console.log('sendEmailJobs Started'); } ``` `起服务时,将功能配置全部模块化,开启服务时,哪个模块有问题就显而易见` ![](http://doc.mvmtv.cn/Public/Uploads/2021-04-02/6066dfb01933d.png) ###🥳Node.js 中的`容错性实在是很弱`,当异步回调中出现异常,或者某个模块某个错误没有被catch,整个服务直接会被挂掉,而且还不好排查,所以我们在编码和规范目录的时候,原则就是遵从分离原则,模块原则,每个模块,功能各司其职,不混在一起,一个好的项目结构,不仅能消除重复代码,提升系统稳定性,改善系统的设计,还能在将来更容易的扩展和维护。👏 ###三、日志搜集 📋 #####1.1 http请求日志记录: ``` 这边使用 express中间件--Morgan,Morgan是一个node.js关于http请求的日志中间件,可以支持自定义记录ip,请求头,状态,响应时间等等 ``` 文档地址:https://www.npmjs.com/package/morgan #####1.2 异常日志记录: 在业务逻辑中,调用第三方模块接口出错,或者链接数据库异常,或者查询异常,查询无果等等都可以把这些异常记录下来,可以自己封装一个中间件方法来记录日志并存储,比如: ``` function setErrorLog(type, msg_en, msg_ch, res_json) { //主动抛出异常日志写入 fs.appendFileSync(path.resolve('/data/www/log-colletion/search.mvmtv.com', `./${todayDate}.log`), `[${nowTime}] ${type} ${msg_en} ${msg_ch} ${res_json} \n`); } ``` ###四、超时处理 🧑🏽‍💻 接口服务正常运行的情况下,都不允许无响应的情况出现,所以一定要做超时处理,包括请求超时,响应超时,比如: ``` //设置响应超时时间(15s) res.setTimeout(15 * 1000, function () { return res.send(globalCode.M_REQUEST_TIMEOUT); }); //设置请求超时时间(20s) req.setTimeout(20*1000); ``` ###五、服务的启动与运行👩🏻‍💻 因为node.js 是单进程,进程被杀死后整个服务就跪了,所以需要进程管理工具 `PM2` 是一个带有负载均衡功能的 Node 应用的进程管理器。并保证进程永远都活着,0 秒的重载,支持自动重启,支持服务日志记录等等 我这边会直接写一个pm2的配置文件procese.json : ``` { "apps": [{ "name": "es-node", "cwd": "/data/www/search.mvmtv.com/", "script": "app.js", "max_memory_restart": "500M", "cron_restart": "1 0 * * *", "watch": true, "exec_interpreter": "node", "exec_mode": "fork", "autorestart": true, "env": { "NODE_ENV": "production" }, "log_date_format":"YYYY-MM-DD HH:mm Z" }] } ``` 具体参见PM2文档:https://www.npmjs.com/package/pm2