09-码路博客《管理员模块》
# 1. 项目介绍
基于Vue+Node.js koa2+mongoose实战开发的⼀套完整的博客项目网站,使用koa2二次开发⼀套适合多端的Restful API, 同时配合完整的后台管理系统。
功能列表
- 管理员与权限控制接口
- 分类接口
- ⽂章管理接口
- 评论/回复功能接口
- ⼴告接口
技术栈
- Node.js
- koa2
- mongodb
- mongoose
注意
- 大家直接基于我发的项目骨架进行开发
- 骨架中,已经定好了所有依赖的版本
- 真实开发中,也是有项目组长搭建好骨架,我们直接开发业务
依赖说明
- bcrypt 对数据进⾏加盐
- 下载 bcrypt 包时,请先全局下载 node-gyp 包,npm i node-gyp -g,下载此包过程有点缓慢,⼤家请耐⼼等待
- jsonwebtoken JSON WEB令牌实现
- koa-jwt Koa JWT身份验证中间件
- koa-bouncer Koa路由的参数验证库
- basic-auth Nodejs基本身份验证解析器
- mongoose MongoDB DOM框架
- redis ⾼性能Redis客户端。
- moment 解析,验证,操作和显示⽇期时间库
- 看到其它模块如果不认识,可以自行查阅,不可能存在一个模块,网上没有任何信息
# 2. 模型(model)创建
根据在上面的介绍,我们本项目中的接口需要有如下:
- 管理员与权限控制接口
- 分类接口
- ⽂章管理接口
- 评论/回复功能接口
- ⼴告接口
根据接⼝,我们可以反映射模型
- 管理员模型
- 分类模型
- ⽂章模型
- 评论模型
- 回复模型
- ⼴告模型
注意
- 数据库模型已经设计好了,大家直接使用
整体项目目录结果如下:
# 3. 管理员注册开发
- router⽂件夹下新建admin.js,代码如下:
// router/admin.js
const Router = require("@koa/router");
const router = new Router();
router.prefix("/admin")
// 注册管理员
router.post("/register", (ctx, next) => {
ctx.body = "册管理员"
})
module.exports = router;
2
3
4
5
6
7
8
9
10
11
12
13
14
- app.js 注册路由
// app.js
// 注意:代码的书写位置
// ...
const admin = require("./router/admin.js")
// ...
// 注册管理员模块路由
app.use(admin.routes())
admin.allowedMethods();
// ...
2
3
4
5
6
7
8
9
10
11
12
13
- postman测试如下:
说明此时路由跑通了。
路由模块接收到请求后,交由controller模块处理,这一层,叫业务逻辑层
在controller文件夹下,创建AdminController.js,代码如下:
// controller/AdminController.js
const AdminModel = require("../models/AdminModel");
const {
registerValidator
} = require("../validators/admin");
class AdminController {
// 注册
static async register(ctx, next) {
// 参数校验
registerValidator(ctx);
// 处理业务
const {
nickname,
password2
} = ctx.request.body;
const currentUser = await AdminModel.findOne({
nickname
});
if (currentUser) {
throw new global.errs.Existing("用户已存在", 900);
}
const user = await AdminModel.create({
nickname,
password: password2,
});
ctx.body = {
code: 200,
msg: "success",
data: user,
errorCode: 0,
};
}
}
module.exports = AdminController;
// 由注册管理员的逻辑看,我们从前台传来的 nickname、password1、password2 字段需要在后端进⾏校验。
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
在路由模块中,使用上面定义好的controller,如下:
// router/admin.js
const Router = require("@koa/router");
const AdminController = require("../controller/AdminController");
const router = new Router();
router.prefix("/admin")
// 注册管理员
// router.post("/register", (ctx, next) => {
// ctx.body = "注册管理员"
// })
// 注册管理员
router.post("/register", AdminController.register);
module.exports = router;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
在postman中测试如下:
到此,用户注册,就实现了。但是上面响应数据,响应的代码如下:
ctx.body = {
code: 200,
msg: "success",
data: user,
errorCode: 0,
};
2
3
4
5
6
那就意味着,后面我们每次响应数据,都要按如上的方式响应,可能比较麻烦,我们可以封装一个返回结果的模块,这个模块已经封装好了,就是core下面的helper.js模块,大家直接使用就OK。修改代码如下:
// controller/AdminController.js
const AdminModel = require("../models/AdminModel");
const {
registerValidator
} = require("../validators/admin");
const res = require("../core/helper");
class AdminController {
// 注册
static async register(ctx, next) {
// 参数校验
registerValidator(ctx);
// 处理业务
const {
nickname,
password2
} = ctx.request.body;
const currentUser = await AdminModel.findOne({
nickname
});
if (currentUser) {
throw new global.errs.Existing("用户已存在", 900);
}
const user = await AdminModel.create({
nickname,
password: password2,
});
// ctx.body = {
// code: 200,
// msg: "success",
// data: user,
// errorCode: 0,
// };
ctx.body = res.json(user);
}
}
module.exports = AdminController;
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
# 4. 管理员登录开发
在router下面的admin.js配置对应的路由,如下:
// router/admin.js
// 管理员登录
router.post('/login', AdminController.login)
2
3
4
在AdminController模块中,准备开发对应的业务逻辑,如下:
// controller/AdminController.js
const AdminModel = require("../models/AdminModel");
const {
registerValidator,
loginValidator
} = require("../validators/admin");
const res = require("../core/helper");
class AdminController {
// 注册
// ...
// 登录
static async login(ctx, next) {
loginValidator(ctx);
const {
nickname,
password
} = ctx.request.body;
console.log(nickname, password);
}
}
module.exports = AdminController;
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
在控制台中,可以得到用户名和密码。由于控制器中得到用户名和密码后,剩下的业务逻辑处理,可能会复杂一点,所以,我们可以理抽取一层,在项目根目录下,创建services文件夹,在这里创建login.js,用来操作管理员登录的业务,目录结构如下:
对应的代码如下:
// services/login.js
const AdminModel = require("../models/AdminModel");
const bcypt = require("bcrypt");
const {
generateToken
} = require('../core/util')
class LoginManager {
static async adminLogin({
nickname,
password
}) {
// 验证用户名和密码是否正确
const user = await AdminModel.findOne({
nickname
});
console.log(user)
if (!user) {
throw new global.errs.AuthFailed("用户名不存在或者密码不正确");
}
const correct = bcypt.compareSync(password, user.password);
if (!correct) {
throw new global.errs.AuthFailed("密码不正确");
}
// 用户名和密码是正确的
// 颁发令牌 生成token
const token = generateToken(user._id)
return {
nickname: user.nickname,
token
}
}
}
module.exports = LoginManager;
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
在controler层使用services层中的login.js,代码如下:
// controller/AdminController.js
const AdminModel = require("../models/AdminModel");
const {
registerValidator,
loginValidator
} = require("../validators/admin");
const res = require("../core/helper");
const LoginManager = require("../services/login");
class AdminController {
// 注册
static async register(ctx, next) {
// 参数校验
registerValidator(ctx);
// 处理业务
const {
nickname,
password2
} = ctx.request.body;
const currentUser = await AdminModel.findOne({
nickname
});
if (currentUser) {
throw new global.errs.Existing("用户已存在", 900);
}
const user = await AdminModel.create({
nickname,
password: password2,
});
// ctx.body = {
// code: 200,
// msg: "success",
// data: user,
// errorCode: 0,
// };
ctx.body = res.json(user);
}
// 登录
static async login(ctx, next) {
loginValidator(ctx);
const {
nickname,
password
} = ctx.request.body;
const user = await LoginManager.adminLogin({
nickname,
password
});
if (!user) {
throw new global.errs.NotFound("用户不存在");
}
ctx.body = res.json(user, "登录成功");
}
}
module.exports = AdminController;
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
postman测试如下:
# 5. 获取⽤户信息
在router目录下的admin.js文件夹下,创建获取用户信息路由,如下:
// router/admin.js
const AdminController = require("../controller/AdminController")
// 获取用户信息
const config = require("../config/index.js");
// ...
router.get(
"/user/info",
jwtAuth({
secret: config.security.secretKey
}),
AdminController.getUserInfo
);
// 注意,此时没有getUserInfo方法,控制台会报错。
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
在AdminController中,创建getUserInfo方法,如下:
// controller/AdminController.js
const AdminModel = require("../models/AdminModel");
const {
registerValidator,
loginValidator
} = require("../validators/admin");
const res = require("../core/helper");
const LoginManager = require("../services/login");
class AdminController {
// 注册
static async register(ctx, next) {
// 参数校验
registerValidator(ctx);
// 处理业务
const {
nickname,
password2
} = ctx.request.body;
const currentUser = await AdminModel.findOne({
nickname
});
if (currentUser) {
throw new global.errs.Existing("用户已存在", 900);
}
const user = await AdminModel.create({
nickname,
password: password2,
});
// ctx.body = {
// code: 200,
// msg: "success",
// data: user,
// errorCode: 0,
// };
ctx.body = res.json(user);
}
// 登录
static async login(ctx, next) {
loginValidator(ctx);
const {
nickname,
password
} = ctx.request.body;
const user = await LoginManager.adminLogin({
nickname,
password
});
if (!user) {
throw new global.errs.NotFound("用户不存在");
}
ctx.body = res.json(user, "登录成功");
}
// 获取用户登录信息 token
static async getUserInfo(ctx, next) {
const _id = ctx.state.user.data;
// 查询用户信息
const userInfo = await AdminModel.findById({
_id
});
if (!userInfo) throw new global.errs.AuthFailed("认证失败,请检查请求头");
ctx.body = res.json({
_id,
nickname: userInfo.nickname
});
}
}
module.exports = AdminController;
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
在postman中测试,如下: