本文讲述有一定web前端基础的小伙伴,如何入坑nodejs。
项目环境:nodejs V12.x,egg V2.x
先列一下大纲:
一,通读nodejs文档
二,通读eggjs文档
三,搭建项目框架
四,文件目录约定
五,部署
六,debug调试方式
七,环境配置
八,参数校验
九,异常捕获
十,状态code拼装
十一,跨域配置
下面看一下正文:
一,通读nodejs文档
http://nodejs.cn/learn
二,通读eggjs文档
https://www.eggjs.org/zh-CN/index
三,搭建项目框架
$ mkdir egg-example && cd egg-example
$ npm init egg --type=simple
$ npm i
四,文件目录约定
demo // 项目根目录
├── app.js // 统一的入口文件
├── package.json // 项目npm配置
├── app
| ├── router.js // 用于配置 URL 路由规则
│ ├── controller // 用于解析用户的输入,处理后返回相应的结果(本工程约定为和接口一一对应)
│ | └── home.js
│ ├── service // 用于编写业务逻辑(比如用户service,则用户的增删改查,都放在这个service中)
│ | └── user.js
│ ├── middleware // 用于编写中间件
│ | └── formatResponseBody.js
│ ├── schedule (待启用) // 用于定时任务
│ | └── my_task.js
│ ├── public (待启用) // 用于放置静态资源
│ | └── reset.css
│ ├── view (待启用) // 用于放置模板文件
│ | └── home.tpl
│ └── extend (待启用) // 用于框架的扩展
│ ├── helper.js (待启用)
│ ├── request.js (待启用)
│ ├── response.js (待启用)
│ ├── context.js (待启用)
├── config
| ├── plugin.js // 用于配置需要挂载的插件
| ├── config.default.js // 默认的配置文件(开发环境的默认配置文件)
│ ├── config.prod.js // 生产环境特殊配置
| ├── config.stg.js // 测试环境特殊配置
| └── config.unittest.js // 单元测试环境特殊配置
└── test // 单元测试
├── middleware // (待启用)
| └── response_time.test.js
└── controller // (待启用)
└── home.test.js
五,部署
nodejs不需要编译,部署就是把本地工程压缩上传到服务器上,解压运行在nodejs环境中,就好了
手动部署过程参考:
1,本地文件压缩
2,登陆服务器,上传压缩文件到服务器
通过ssh工具,MobalXterm或者ZenTermLite等
3,docker hub上搜索node镜像,选择需要的nodejs版本
docker search node
5,拉取镜像,如果没有特别要求,可以直接拉取latest版本
docker pull node:latest
6,建立并启动容器
docker run -itd --name web-nodejs-server -p 8080:80 node:latest
解释:
a,-i 以交互模式运行容器,通常与 -t 同时使用;
b,-t 为容器重新分配一个伪输入终端,通常与 -i 同时使用;
c,-d 后台运行容器,并返回容器ID;
d,name 容器名称
e,-p 8080:80: 端口进行映射,将本地 8080 端口映射到容器内部的 80 端口。
f,最后是使用的 镜像名称
7,进入容器
docker exec -it web-nodejs-server /bin/bash
8,建立项目运行文件夹
cd /usr/src/
mkdir server
9,退出容器,将项目文件全部转移到 以上目录中
exit
docker cp ./askbob-web-node-server web-nodejs-server:/usr/src/
10,进入容器,启动服务
docker exec -it web-nodejs-server /bin/bash
npm run start
六,调试方式
开启 VSCode 配置 Debug:
Auto Attach开启为on,然后在 Terminal 执行 npm run debug 即可。
操作步骤:
(1)vscode—setting---搜索 attach
(2)Debug › Node: Auto Attach
(3)开启为on
(4)重启vs code
(5)npm run debug
七,环境配置
// 运行开发环境
// 环境配置config.default.js, 环境标示app.config.env=local
$ npm run dev
// 运行调试环境, vs code断点有效,
// 环境配置config.default.js, 环境标示app.config.env=local
$ npm run debug
// 运行测试环境,环境配置config.stg.js, 环境标示app.config.env=stg
$ npm run stg
// 运行生产环境,环境配置config.prod.js, 环境标示app.config.env=prod
$ npm run prod
├── config
| ├── plugin.js // 用于配置需要挂载的插件
| ├── config.default.js // 默认的配置文件(开发环境的默认配置文件)
│ ├── config.prod.js // 生产环境特殊配置
| ├── config.stg.js // 测试环境特殊配置
| └── config.unittest.js // 单元测试环境特殊配置
八,参数校验:使用egg-validate插件
// 安装依赖包
npm i egg-validate
// 装载插件 路径:/config/plugin.js
validate: {
enable: true,
package: 'egg-validate',
},
// 配置插件 路径:/config/config.default.js
config.validate = {
// 会对入参进行转换
// 举个例子,使用表单中默认的 submit 类型按钮提交表单时,
// 提交过来的往往是序列化后的字符串,那些期望是数字类型的字段就会始终验证不过。
// 而开启此项后,会对入参按希望的类型进行转换。
convert: true,
// 开启后,会把空字符串,NaN,null 这些转成 undefined,
// 将这些异常的数据进行了统一,方便后续处理。
widelyUndefined:true
// 限制被验证值必须是一个对象
// validateRoot: false,
}
// 使用参数校验插件 路径: /app/controller/*.js
// 定义参数校验规则
const validateRules = {
channel: {
type: 'string',
required: true,
min: 3,
max: 3,
},
// string?---字符串类型,可选,如果必须,去掉?
phone: 'string?',
eventId: 'string?',
}
// 对ctx.query中的参数进行校验
const errors = this.app.validator.validate(validateRules, ctx.query);
if (errors) {
// 如果校验失败,在这里做一些处理
}
九,异常捕获
参考:https://www.eggjs.org/zh-CN/core/error-handling
(1)使用try catch
(2)框架层统一异常处理___项目自带的onerror插件
(3)onerror 插件支持自定义配置错误处理方法,本项目配置见如下代码
// 配置插件 路径:/config/config.default.js
config.onerror = {
all(err, ctx) {
// 吸收程序中所有未catch的err,并在body中返回错误堆栈信息
ctx.body = err.stack;
ctx.status = 500;
}
}
十,状态code拼装___使用自定义的formatResponseBody中间件
说明:
(1) Egg 是基于 Koa 实现的,所以 Egg 的中间件形式和 Koa 的中间件形式是一样的,都是基于洋葱圈模型。每次我们编写一个中间件,就相当于在洋葱外面包了一层。
(2) 简单理解:以router中的使用为例,await next() 前面的代码在进入controller之前执行,await next()后面的代码在从controller回来之后执行;
参考文章:
(1) koa2中间件机制-洋葱圈模型 https://www.cnblogs.com/senjer/p/10621980.html
(2) nodejs中的中间件是什么意思
https://www.php.cn/website-design-ask-483598.html
// 使用方式1:路由中为单个接口配置
// 使用中间件 路径:/app/router.js
module.exports = app => {
const { router, controller, middleware } = app;
......
router.post(
'/askbob-admin/updateTrackWhitelist',
middleware.formatResponseBody(),
controller.updateTrackWhitelist.index);
};
// 使用方式2:全局使用
// 使用中间件 路径:config.default.js
// 数组顺序即为中间件的加载顺序
config.middleware = ['formatResponseBody'];
// 编写中间件 路径:/app/middleware/formatResponseBody.js
module.exports = () => {
return async function formatResponseBody(ctx, next) {
await next();
// 定义生成searchI的方法
function createRequestId(length) {
let str = ''
const arr = ['0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
for (let i = 0; i < length; i++) {
const pos = Math.round(Math.random() * (arr.length - 1))
str += arr[pos]
}
return str
}
// 定义返回状态配置
// 配置说明:配置同java服务端,目前使用了200和400
const responseStatusMap = {
'SUCCESS': {
code: '200',
msg: '接口请求成功!'
},
'INVALID_PARAM': {
code: '400',
msg: '字段校验非法!'
},
'INVALID_REQUEST_MSG': {
code: '402',
msg: '请求数据非法!'
},
'SERVICE_NOT_FOUND': {
code: '404',
msg: '服务未发现!'
},
'RUNTIME_EXCEPTION': {
code: '500',
msg: '运行时异常!'
},
'BUSINESS_PROCESS_FAILED': {
code: '501',
msg: '服务器繁忙!'
},
'GATEWAY_EXCEPTION': {
code: '502',
msg: '网关繁忙!'
},
'CLIENT_ABORT': {
code: '502',
msg: '请求超时!'
},
'INVALID_VIRTUAL_USER': {
code: '940304',
msg: '非法虚拟用户!'
},
'INVALID_REQUEST': {
code: '940405',
msg: '请求方式错误!'
},
'DIRECT_REQUEST_NOT_SUPPORT': {
code: '940428',
msg: '不允许直接访问!'
},
'FLOW_LIMIT': {
code: '950429',
msg: 'Blocked by Sentinel (flow limiting)!'
}
}
// 依据status获取配置
const responseStatus = responseStatusMap[ctx.body.status]
// 将返回状态配置和requestId组装入返回体中
// 右侧ctx.body内容约定:SUCCESS状态下controller/service返回data信息,其他状态下返回errors信息
ctx.body = Object.assign(ctx.body, responseStatus, {requestId: createRequestId(19)})
};
};
十一,跨域配置
// 安装依赖包
npm i egg-cors
// 装载插件 路径:/config/plugin.js
cors: {
enable: true,
package: 'egg-cors',
},
// 配置插件 路径:/config/config.default.js
// 简单请求和复杂请求,参考博文 https://www.cnblogs.com/goloving/p/14525157.html
// 简单请求,只要设置如下相应头即可
// 1,origin: '*',
// 2,allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH',
// 复杂请求,必须指定origin为某个具体的请求源,以及处理其他导致请求成为复杂请求的问题
// 管理后台项目请求,有如下请求变动,导致变为复杂请求,需要处理
// 1,request header新增/改写Authorization和Content-Type,
// 所以需要明文指定allowHeaders: 'Authorization, Content-Type',
// 2,withCredentials: true 设置了携带cookie,所以响应需要设置credentials: true,
config.cors = {
origin:'http://127.0.0.1:8013',
allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH',
allowHeaders: 'Authorization, Content-Type',
credentials: true,
// 设置max age,浏览器端会进行缓存,没有过期之前真对同一个请求只会发送一次预检请求
maxAge: 86400,
}
后面的计划(完成后 会继续更新本文):
1,测试用例编写
2,登陆认证