四、从零模拟新浪微博-用户管理
代码仓库:https://github.com/changeclass/koa2-weibo
数据同步
user表数据模型
1 | /** |
分层
业务分层
1 | src |
将各个功能模块进行分层可以是代码逻辑更加清晰。定义业务模型层,例如错误信息(统一返回格式)。定义server
层用于与数据库进行交互。
业务模型层(统一返回格式)
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/**
* @description: res的数据模型
* @author: 小康
* @url: https://xiaokang.me
* @Date: 2020-12-17 14:44:52
* @LastEditTime: 2020-12-17 14:44:52
* @LastEditors: 小康
*/
/**
* @author: 小康
* @url: https://xiaokang.me
* @description: 基础模块
*/
class BaseModel {
constructor({ errno, data, message }) {
this.errno = errno
if (data) {
this.data = data
}
if (message) {
this.message = message
}
}
}
/**
* @author: 小康
* @url: https://xiaokang.me
* @description: 成功的模型
*/
class SuccessModel extends BaseModel {
constructor(data = {}) {
super({
errno: 0,
data
})
}
}
/**
* @author: 小康
* @url: https://xiaokang.me
* @description: 失败的模型
*/
class ErrorModel extends BaseModel {
constructor({ errno, message }) {
super({
errno,
message
})
}
}
module.exports = {
SuccessModel,
ErrorModel
}业务模型层(失败信息集合)
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
69
70
71
72
73
74
75
76
77
78
79
80
81/**
* @description: 失败信息集合
* @author: 小康
* @url: https://xiaokang.me
* @Date: 2020-12-17 14:54:07
* @LastEditTime: 2020-12-17 14:54:08
* @LastEditors: 小康
*/
module.exports = {
// 用户名已存在
registerUserNameExistInfo: {
errno: 10001,
message: '用户名已存在'
},
// 注册失败
registerFailInfo: {
errno: 10002,
message: '注册失败,请重试'
},
// 用户名不存在
registerUserNameNotExistInfo: {
errno: 10003,
message: '用户名未存在'
},
// 登录失败
loginFailInfo: {
errno: 10004,
message: '登录失败,用户名或密码错误'
},
// 未登录
loginCheckFailInfo: {
errno: 10005,
message: '您尚未登录'
},
// 修改密码失败
changePasswordFailInfo: {
errno: 10006,
message: '修改密码失败,请重试'
},
// 上传文件过大
uploadFileSizeFailInfo: {
errno: 10007,
message: '上传文件尺寸过大'
},
// 修改基本信息失败
changeInfoFailInfo: {
errno: 10008,
message: '修改基本信息失败'
},
// json schema 校验失败
jsonSchemaFileInfo: {
errno: 10009,
message: '数据格式校验错误'
},
// 删除用户失败
deleteUserFailInfo: {
errno: 10010,
message: '删除用户失败'
},
// 添加关注失败
addFollowerFailInfo: {
errno: 10011,
message: '添加关注失败'
},
// 取消关注失败
deleteFollowerFailInfo: {
errno: 10012,
message: '取消关注失败'
},
// 创建微博失败
createBlogFailInfo: {
errno: 11001,
message: '创建微博失败,请重试'
},
// 删除微博失败
deleteBlogFailInfo: {
errno: 11002,
message: '删除微博失败,请重试'
}
}
检查用户是否存在
API路由层
1
2
3
4
5// 用户名是否存在
router.post('/isExist', async (ctx, next) => {
const { userName } = ctx.request.body
ctx.body = await isExist(userName)
})controller
层1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23const { getUserInfo, createUser } = require('../services/user')
const { SuccessModel, ErrorModel } = require('../model/ResModel')
const {
registerUserNameNotExistInfo,
registerUserNameExistInfo,
registerFailInfo
} = require('../model/ErrorInfo')
/**
* @author: 小康
* @url: https://xiaokang.me
* @param {String} userName 需要检查的用户名
* @description: 检查用户名是否存在
*/
async function isExist(userName) {
const userInfo = await getUserInfo(userName)
if (userInfo) {
// 已经存在
return new SuccessModel(userInfo)
} else {
// 不存在
return new ErrorModel(registerUserNameNotExistInfo)
}
}servers
层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
32const { User } = require('../db/model/index')
const { formatUser } = require('./_format')
/**
* @author: 小康
* @url: https://xiaokang.me
* @param {*} userName 用户名
* @param {*} password 密码
* @description: 获取用户的信息
*/
async function getUserInfo(userName, password) {
const whereOpt = {
userName
}
if (password) {
Object.assign(whereOpt, { password })
}
// 查询
const result = await User.findOne({
// 查询的列
attributes: ['id', 'userName', 'nickName', 'picture', 'city'],
// 查询条件
where: whereOpt
})
if (result == null) {
// 未找到
return result
}
// 格式化
const formatRes = formatUser(result.dataValues)
return formatRes
}formatUser
方法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/**
* 格式化头像
* @author 小康
* @date 2020-12-17
* @param {Object} obj 用户对象
* @returns {Object} 处理后的结果
*/
function _formatUserPicture(obj) {
if (obj.picture == null) {
obj.picture = DEFAULT_PICTURE
}
return obj
}
/**
* 格式化用户
* @author 小康
* @date 2020-12-17
* @param {Array|Object} list 用户列表或单个用户对象
* @returns {any}
*/
function formatUser(list) {
if (list == null) {
return list
}
if (list instanceof Array) {
// 数组 用户列表
return list.map(_formatUserPicture)
}
// 单个对象
let result = list
result = _formatUserPicture(result)
return result
}
用户注册
具体逻辑与检查用户是否存在相似。
密码加密
密码加密只需要在存储数据时对数据进行加密处理即可。
user
的controller
层
1 | // ... |
doCrypto
函数
1 | /** |
用户信息格式验证
在注册逻辑执行前加入中间件函数。
1 | // 注册路由 |
genValidator
用于生成中间件函数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/**
* @description: json schema验证中间件
* @author: 小康
* @url: https://xiaokang.me
* @Date: 2020-12-17 16:34:54
* @LastEditTime: 2020-12-17 16:34:54
* @LastEditors: 小康
*/
const { jsonSchemaFileInfo } = require('../model/ErrorInfo')
const { ErrorModel } = require('../model/ResModel')
/**
* @author: 小康
* @url: https://xiaokang.me
* @param {function} validateFn 验证函数
* @description: 生成json schema 验证中间件
*/
function genValidator(validateFn) {
async function validator(ctx, next) {
const data = ctx.request.body
const error = validateFn(data)
if (error) {
// 验证失败
return (ctx.body = new ErrorModel(jsonSchemaFileInfo))
}
// 验证成功
await next()
}
return validator
}
module.exports = { genValidator }传入验证函数
userValidate
验证函数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/**
* @description: user 数据格式校验
* @author: 小康
* @url: https://xiaokang.me
* @Date: 2020-12-17 16:17:42
* @LastEditTime: 2020-12-17 16:17:43
* @LastEditors: 小康
*/
const validate = require('./_validate')
// 校验规则
const SCHEMA = {
type: 'object',
properties: {
userName: {
type: 'string',
pattern: '^[a-zA-Z][a-zA-Z0-9_]+$', // 字母开头,字母数字下划线
maxLength: 255,
minLength: 2
},
password: {
type: 'string',
maxLength: 255,
minLength: 3
},
newPassword: {
type: 'string',
maxLength: 255,
minLength: 3
},
nickName: {
type: 'string',
maxLength: 255
},
picture: {
type: 'string',
maxLength: 255
},
city: {
type: 'string',
maxLength: 255,
minLength: 2
},
gender: {
type: 'number',
minimum: 1,
maximum: 3
}
}
}
/**
* 校验用户数据格式
* @param {Object} data 用户数据
*/
function userValidate(data = {}) {
return validate(SCHEMA, data)
}
module.exports = userValidate
登录验证中间件
1 | /** |
单元测试
数据模型
model.test.js
1 | /** |
登录相关测试
1 | /** |
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 小康博客!
评论
TwikooWaline