插件的使用
Egg 使用 Koa2 中间件
在 config 文件夹
config.default.js
中设置1
2// add your config here
config.middleware = ["jwt"];然后在 app/middleware 目录下面(没有则新建) 新建一个上面数组里的同名文件,比如 jwt.js ,然后写入中间件内容即可。
1
2// jwt.js
module.exports = require("koa-jwt");
☆ 不清楚这种插件是否也是像this.app.**
这么使用,并且 config.js 里面配置的插件参数是否可用。
使用插件解决本地开发跨域问题(csrf)
npm i egg-security egg-cors -S
```js
// plugin.js
exports.security = {
enable: true,
package: “egg-security”
};exports.cors = {
enable: true,
package: “egg-cors”
};1
2
3
4
5
6
7
8
9
10
11
12
13
14
运行时有黄色警告可能是`npm i`的时候自动将 security 插件一起装了,提示重复配置,注释了就行。
3. ```js
// config.default.js
config.security = {
csrf: false,
ctoken: false,
domainWhiteList: ["http://127.0.0.1:7001", "http://192.168.1.101:7001"]
};
config.cors = {
origin: "*",
allowMethods: "GET,HEAD,PUT,POST,DELETE,PATCH"
};
使用 Sequelize 操作 MySQL 数据库
添加 mysql 路径
export PATH=${PATH}:/usr/local/mysql/bin
source ~/.bash_profile
或者source ~/.zshrc
mysql -u root -p
输入密码进入创建一个数据库
create database demo;
```js
const Sequelize = require(“sequelize”);
const sequelize = new Sequelize(“study01”, “root”, “965516531”, {
host: “localhost”,
dialect: “mysql”,
pool: {max: 5, min: 0, acquire: 30000, idle: 10000
},
operatorsAliases: false
});const User = sequelize.define(“user”, {
username: Sequelize.STRING,
birthday: Sequelize.DATE
});sequelize
.sync()
.then(() =>User.create({ username: "demo", birthday: new Date(1980, 6, 20) })
)
.then(jane => {console.log(jane.toJSON());
});
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
使用此 js 文件测试数据库是否连接成功。如果使用 mysql8 版本创建数据库,选择使用宽松模式而不是强密码模式,因为大部分第三方库还未对强密码模式进行适配。
如果使用TS的egg-mysql,需要在src/typings.d.ts里面添加上一段ts的定义,不然会报`类型“Application”上不存在属性“mysql”。`。
```ts
import 'egg';
declare module 'egg' {
interface mysql {
get(tableName: String, find: {}): Promise
query(sql: String, values: Any[]): Promise
}
interface Application {
mysql: mysql;
}
}
2021-11更新
使用egg-sequesize链接更为简单,只需要按照文档配置一下,然后写对应的model就可以使用了。注意的是可能需要将时间戳禁用掉
1 | config.sequelize = { |
使用 egg-redis 连接 redis 数据库
- 安装
brew install redis
- 启动
brew services start redis
- 停止
brew services stop redis
- 或者使用 redis 自带的启动停止
redis-server
redis-server --port 6380
redis-cli shutdown
- 用法是
this.app.redis
这样使用
redis 常用命令
- 使用
redis-cli
进入 redis 客户端 keys *
获取所有的 key 值get key
获取 key 对应的值flushall
清除所有数据库的所有数据
使用 egg-mongoose 连接 mongo 数据库
npm i egg-mongoose --save
1 | // plugin.js |
1 | // {app_root}/config/config.default.js |
1 | // {app_root}/app/model/user.js |
一开始建集合之后填入数据老是报一个错E11000 duplicate key error collection
,解决方法之一是删了集合重新弄。
mongoose 使用技巧
一个值多个字段进行查询
1
2
3
4
5
6
7let user = await ctx.model.User.find({
$or: [
{ phoneNum: parseInt(ctx.request.body.username) },
{ userName: ctx.request.body.username },
{ email: ctx.request.body.username }
]
});查询返回指定字段
find 方法第一个参数表示查询条件,第二个参数用于控制返回字段,第三个参数用于配置查询参数,第四个参数是回调。如果使用第三个参数而不用第二个参数,那么设置为 null。
如果指定某些字段返回那么设置那个字段值为 1,如果指定某些字段不返回设置值为 0let user: any = await ctx.model.User.find({ _id: ok.id }, { password: 0 })
,
导入数据
在数据文件目录使用命令导入数据表 mongoimport -d meituan -c areas areas.dat
meituan 是数据库名字 areas 是集合(表名) areas.dat 是数据文件。
使用 egg-jwt 进行 Token 的分发
npm install egg-jwt --save
- ```js
// {app_root}/config/plugin.js
exports.jwt = {
enable: true,
package: “egg-jwt”
};1
2
3
4
53. ```js
// {app_root}/config/config.default.js
exports.jwt = {
secret: "123456" //自己设置的值
}; - 分发 Token
const token = app.jwt.sign(userToken, secret, {expiresIn: '1h'})
- 校验 Tokne
const token = ctx.header.authorization
- 解密 Token
let payload = await app.jwt.verify(token.split(' ')[1], secret) // // 解密,获取payload
JWT Tokne 的刷新和 sso 单点登录问题
使用 Token 进行权限验证,为了防止重放攻击所以一般 token 设置一个过期时间。假如设置 1 小时过期时间,而用户在一小时内不停操作,但是 token 失效,这时候用户就被迫进行重新登录,这是不行的,所以需要 token 刷新。
假设一个设备登录 此 id 生成一个 token 正常:验证没问题 超时:redis 找这个 token 的数据 查里面的长 tokne 刷新时间 长 token 过期 app 登录 ❌
假设设备登录获取一个 token 时效 30d 未过期登录 查 redis 里面的 id 验证是不是一个 token 不是的话 说明这是老旧的未过期的 tokne 然后直接让让 app 重新登录 登录的时候服务端刷新 token 保证最新 这是 sso 单点登录
但是假如这个 app 在 29 点最后时刻在使用 这时候 token 过期了 服务端返回 token 过期提示 然后 app 根据状态吗重新请求 token ❎
直接重新登录 但是这个 token 设置的可以很长
这个地方我也奇怪为什么有两个 tokne 的说法:
JWT 为什么要设置 2 个 token?
参考资料
使用 UUID 包进行 uuid 的创建
使用 crypto-js 进行密码的加密
使用crypto-js
模块进行密码加密。
npm i -save crypto-js
let CryptoJS = require('crypto-js')
CryptoJS.MD5(***).toString()
使用 egg-validate 进行数据的校验
npm i egg-validate --save
1 | // config/plugin.js |
1 | // config/config.default.js |
使用方式:
1 | const rule = { |
若是 error 不为空,那么就是校验未通过,若是为空就是校验通过。
配置项及校验方式等写法参考 parameter:
parameter
使用 passport 来进行用户的(第三方)登录鉴权
不过这个插件的使用,好像是基于使用 egg 来渲染页面然后进行鉴权的方式,而 spa 大多使用 Token 的方式来进行验证。
参考资料
在 Mac 下安装 MySQL
Mac 电脑安装及终端命令使用 mysql
TSLint 设置
引入一些没有 d.ts 文件的模块时提示不允许使用 require。解决办法:
"no-var-requires": false,
必须使用单引号,jsx 中必须使用双引号,去掉 singel 就可以
1
2
3
4
5"quotemark": [
true,
"single",
"jsx-double"
],对尾随逗号的校验
1
2
3
4
5
6
7
8
9"trailing-comma": [true, { //对尾随逗号的校验
"multiline": {
"objects": "ignore",
"arrays": "ignore",
"functions": "ignore",
"typeLiterals": "ignore"
},
"esSpecCompliant": true //是否允许尾随逗号出现在剩余变量中
}]行尾是否以空格结尾(设置成 false 就不用必须加分号)
1
2
3
4
5"no-trailing-whitespace": [// 不允许空格结尾
true,
"ignore-comments",
"ignore-jsdoc"
],
优质资源
《Node.js 实战(egg+vue)》
- 书里还有发送邮件的方式 暂时不看
egg-shell-decorators(蛋壳)
Egg.js 路由装饰器,让你的开发更敏捷~
自带路由解析和 Swagger。
蛋壳
npm install egg-shell-decorators -S
```ts
// app/router.ts
import { Application } from “egg”;
import { EggShell } from “egg-shell-decorators”;export default (app: Application) => {
EggShell(app, { prefix: “/“, quickStart: true });
};1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
3. demo
```ts
// app/controller/user.ts
import { Controller } from "egg";
import {
Get,
IgnoreJwtAll,
Description,
TagsAll
} from "egg-shell-decorators";
@TagsAll("用户")
@IgnoreJwtAll
export default class SubOrderController extends Controller {
@Get("/:id")
@Description("根据id获取用户详情")
public listUser({ params: { id } }) {
return {
id
};
}
}添加 swagger-ui
node-swagger-ui
作者提供的汉化版 swagger-ui 地址,并且附带使用 express 启动的 index.js 文件。
将整个项目 clone 下来,放到 app 目录下面 api-docs 文件夹里面并npm i
。router.js 改造
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29import { Application } from "egg";
import { EggShell } from "egg-shell-decorators";
export default (app: Application) => {
EggShell(app, {
prefix: "/",
quickStart: true,
swaggerOpt: {
open: true,
title: "示例",
version: "1.0.0",
host: "127.0.0.1",
port: 7001,
schemes: ["http"],
paths: {
outPath: "./api-docs/public/json/main.json",
definitionPath: "app/definitions",
swaggerPath: "app/swagger"
},
tokenOpt: {
default: "manager",
tokens: {
manager: "123",
user: "321"
}
}
}
});
};ok 这样使用装饰器写的路由便会自动生成可以被 swagger 使用的 json 文档。
在 api-docs 目录里面使用 pm2 启动 index.js,
pm2 start index.js
,没装 pm2 的话 安装一下npm i pm2 -g
localhost:3001 完工
如果你想尽量少些装饰器,使得 controller 看起来不那么臃肿,那么你也可以分开写。将模型写到
app/definitions
,1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25// app/definitions/user.json
{
"User": {
"type": "object",
"properties": {
"userName": {
"type": "string",
"description": "姓名"
},
"phoneNum": {
"type": "integer",
"format": "int32",
"description": "手机号码"
},
"email": {
"type": "string",
"description": "邮箱"
},
"password": {
"type": "string",
"description": "密码"
}
}
}
}swagger 的 req 和 res 框,写在
app/swagger
里面1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29// app/swagger/user.json
{
"/SignUp": {
"post": {
"description": "用户注册",
"parameters": [
{
"name": "body",
"in": "body",
"required": true,
"schema": {
"$ref": "User"
}
}
],
"responses": {
"type": "object",
"schema": {
"$ref": "User"
}
}
}
},
"/VerificationCode": {
"get": {
"description": "发送验证码"
}
}
}保持和 controller 文件夹里面的文件名一致,因为使用了路由映射。
controller/user.ts1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63import { Controller } from "egg";
import { Post, Get } from "egg-shell-decorators";
export default class UserController extends Controller {
"/VerificationCode") (
public async verificationCode() {
let code = new Date().getTime();
return {
phoneNum: this.ctx.query.phoneNum || "",
code,
msg: "验证码发送成功!"
};
}
"/SignUp") (
public async singup() {
const { ctx, app } = this;
const rule = {
phoneNum: "int",
email: {
type: "email",
required: false,
default: ""
},
userName: {
type: "string",
required: false,
default: ""
},
password: {
type: "password"
}
};
const error = app.validator.validate(rule, ctx.request.body);
if (error) {
return {
code: -1,
msg: error
};
} else {
let user = await ctx.model.User.find({
phoneNum: ctx.request.body.phoneNum
});
if (user.length) {
return {
code: -1,
msg: "账号已经注册!"
};
} else {
let nuser = await ctx.model.User.create(ctx.request.body);
if (!nuser) {
return {
code: -1,
msg: nuser
};
} else {
return {
code: 0,
msg: "注册成功!"
};
}
}
}
}
}QuickStart 模式会自动帮助我们处理响应体,但这会导致多一层数据嵌套,可以选择在配置里将 QuickStart
如果使用 egg-jwt 做 token 校验,我们可以使用
IgnoreJwt
装饰器对当前路由进行忽略校验,使用IgnoreJwtAll
可以对 controller 都进行忽略校验。★★★ 有个问题注意下 蛋壳不是洋葱圈模型而是类似于注入,因此无法在中间件进行拦截,所以上面的 egg-jwt 校验也无法生效,需要手动用 egg 原生的中间件进行校验。 作者大大说找时间解决,waining…
EggJS 项目部署
typescript代码下的项目部署
npm install --production
可以执行这个命令也可以不执行,执行这个命令只会安装生产用的安装包并压缩。如果不执行这个命令,可以提高本地压缩效率,但是需要生产解压缩之后装一下 ets(npm i
) 用来跑npm run tsc
,因为需要将ts文件构建成js文件。- 拿到编译构建完之后的项目,使用
npm start
即可在生产环境运行此项目。具体的命令配置可以参考 文档 - 使用
npm stop
可以停止服务运行总结
做登录的时候有一个重要的问题,就是 token 的刷新问题。具体解决方法见上面 jwt 模块。