基于Egg实现基于JWT的RESTful API

本文最后更新于:2023年3月19日 晚上

前言

本文将基于 Egg.js,使用 Typescript,实现基于 JWT 的 RESTful API。使用到的插件有:egg-mysqlegg-sequelizeegg-validate-joiegg-jwtegg-corsegg-router-plusegg-swagger-doc
项目源码请访问:https://github.com/HaisawaEtsu/egg-test-demo

相关插件配置

插件安装

相关 ts 目录规范、插件安装及配置教程请参考官网及 npmjs 文档:
egg-typescript
egg-mysql
egg-sequelize
RESTFul API
egg-validate-joi
egg-router-plus
egg-swagger-doc

egg-jwt

1.使用 npm 安装egg-corsegg-jwt

1
npm install egg-cors egg-jwt --save

2.config/plugin.ts中添加相应的配置

1
2
3
4
5
6
7
8
9
10
11
// config/plugin.ts
...
jwt: {
enable: true,
package: 'egg-jwt',
},
cors: {
enable: true,
package: 'egg-cors',
},
...

3. config/config.default.ts  添加相应的配置

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// config/config.default.ts
...
config.jwt = {
secret: 'r1Wp3kxj3TmaUrruPwyZzNgkaxepMHyo', // 自定义token secret
};

config.security = {
csrf: {
enable: false,
ignoreJSON: true,
},
domainWhiteList: [ 'http://localhost' ], // 接口白名单
};
config.cors = {
origin: '*',
allowMethods: 'GET,HEAD,PUT,POST,DELETE,PATCH',
};
...

配置文件

config/plugin.ts

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
30
31
32
33
34
35
// config/plugin.ts
import { EggPlugin } from "egg";

const plugin: EggPlugin = {
static: true,
mysql: {
enable: true,
package: "egg-mysql",
},
sequelize: {
enable: true,
package: "egg-sequelize",
},
validate: {
enable: true,
package: "egg-validate",
},
jwt: {
enable: true,
package: "egg-jwt",
},
cors: {
enable: true,
package: "egg-cors",
},
routerPlus: {
enable: true,
package: "egg-router-plus",
},
swaggerdoc: {
enable: true,
package: "egg-swagger-doc",
},
};
export default plugin;

路由及路由映射建立

使用egg-router-plus进行路由拆分,这个插件会自动读取app/router下的分路由 ts 文件

1
2
3
4
5
6
7
8
9
10
11
// app/router/authorization.ts
import { Application } from "egg";

export default function (app: Application) {
const { controller, router } = app;
router.resources(
"authorization",
"/api/v1/authorization",
controller.authorization
);
}

再需要权限验证的路由,引入 jwt 中间件

1
2
3
4
5
6
7
// app/router/user.ts
import { Application } from "egg";

export default function (app: Application) {
const { controller, router, jwt } = app;
router.resources("user", "/api/v1/user", jwt, controller.user);
}

登录鉴权接口的实现

Controller 及 Service 的编写

本项目中同时引入了egg-swagger-doc建立 api 接口文档,具体前置操作可以查看文档。

Controller

使用egg-swagger-doc,可以使用 jsDoc 的形式,结合egg-swagger-doccontract中定义的规则,可以在页面中自动生成接口文档相关信息。
同时使用了/extend/helper.js/统一封装了正确及错误返回方法,使用ctx.helper.success以及ctx.helper.fail来调用,具体实现请查看源码。
使用了egg-validate-joi 进行参数合法校验,如果参数不合法使用ctx.helper.validateError返回错误结果。

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
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import { Controller } from "egg";

/**
* @Controller authorization
*/
export default class AuthorizationController extends Controller {
/**
* @summary 用户登录
* @description 用户登录,获取用户token
* @router post /api/v1/authorization
* @request body authorizationUserRequest *body
* @response 200 baseResponse 用户token
*/
public async create() {
const { ctx, app } = this;
const { loginName, password } = ctx.request.body;
const { Joi } = app;
const errors = ctx.validateJoi({
body: {
loginName: Joi.string().required(),
password: Joi.string().required(),
},
});
if (errors) {
ctx.helper.validateError(ctx, errors);
return;
}
// 判断用户名密码是否正确
const validUser = await ctx.service.user.validUser(loginName, password);
if (validUser.isValid) {
const { user } = validUser;
// 生成用户token
const token = app.jwt.sign(
{
username: user.username,
u_id: user.u_id,
},
app.config.jwt.secret
);

// 返回token
ctx.helper.success(ctx, "ok", { token });
}
}
}

Service

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
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
63
64
65
66
67
68
import { Service } from "egg";
import crypto = require("crypto");

type validUser = {
isValid: boolean;
user?: any;
};

export default class UserService extends Service {
public async validUser(
loginName: string,
password: string
): Promise<validUser> {
const { ctx } = this;
const user = await this.findByLoginName(loginName);

if (user) {
const pwd = crypto.createHash("md5").update(password).digest("hex");
const u = user.get();
if (u.password === pwd) {
return {
isValid: true,
user: u,
};
}
ctx.helper.fail(ctx, {
code: 0,
msg: "密码错误",
});
return {
isValid: false,
};
}
ctx.helper.fail(ctx, {
code: 0,
msg: "用户名不存在",
});
return {
isValid: false,
};
}

public async findByLoginName(loginName: string) {
const { ctx } = this;
const res = await ctx.model.User.findOne({
where: {
login_name: loginName,
},
});
if (res) {
return res;
}
return null;
}

public async findByUsername(username: string) {
const { ctx } = this;
const res = await ctx.model.User.findOne({
where: {
username,
},
});
if (res) {
return res;
}
return null;
}
}

使用 swagger 进行测试

默认地址为http://localhost:7001/swagger-ui.html


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!