09-码路博客《管理员模块》

7/25/2022

# 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. 管理员注册开发

  1. 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;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
  1. app.js 注册路由
// app.js  
// 注意:代码的书写位置

// ...
const admin = require("./router/admin.js")

// ...

// 注册管理员模块路由
app.use(admin.routes())
admin.allowedMethods();

// ...
1
2
3
4
5
6
7
8
9
10
11
12
13
  1. 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 字段需要在后端进⾏校验。
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

在路由模块中,使用上面定义好的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;
1
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,
};
1
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;
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

# 4. 管理员登录开发

在router下面的admin.js配置对应的路由,如下:

// router/admin.js

// 管理员登录
router.post('/login', AdminController.login)
1
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;
1
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;
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

在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;
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

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方法,控制台会报错。
1
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;
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

在postman中测试,如下:

Last Updated: 12/25/2022, 10:02:14 PM