@kne/fastify-account 2.0.0 → 2.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +48 -6
- package/libs/models/user.js +27 -51
- package/libs/services/account.js +1 -1
- package/libs/services/user.js +28 -30
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,18 +1,43 @@
|
|
|
1
1
|
|
|
2
|
-
# fastify-
|
|
2
|
+
# fastify-account
|
|
3
3
|
|
|
4
4
|
|
|
5
5
|
### 描述
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
用于用户注册登录认证.
|
|
8
8
|
|
|
9
9
|
|
|
10
10
|
### 安装
|
|
11
11
|
|
|
12
12
|
```shell
|
|
13
|
-
npm i --save @kne/fastify-
|
|
13
|
+
npm i --save @kne/fastify-account
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
+
|
|
17
|
+
### 概述
|
|
18
|
+
|
|
19
|
+
#### 功能模块
|
|
20
|
+
- **账号管理**:提供用户注册、登录、密码修改、忘记密码等功能。
|
|
21
|
+
- **用户信息管理**:支持用户信息的获取和更新。
|
|
22
|
+
- **验证码服务**:支持邮箱和短信验证码的发送与验证。
|
|
23
|
+
|
|
24
|
+
#### 技术栈
|
|
25
|
+
- **后端框架**:Fastify
|
|
26
|
+
- **数据库**:Sequelize(支持多种数据库)
|
|
27
|
+
- **认证**:JWT(JSON Web Token)
|
|
28
|
+
- **密码加密**:bcryptjs
|
|
29
|
+
- **工具库**:Lodash、Day.js
|
|
30
|
+
|
|
31
|
+
#### 核心模块
|
|
32
|
+
- **models**:定义数据模型(用户、账号、验证码等)。
|
|
33
|
+
- **services**:业务逻辑层,处理核心功能。
|
|
34
|
+
- **controllers**:API接口层,定义路由和请求处理。
|
|
35
|
+
|
|
36
|
+
#### 项目特点
|
|
37
|
+
- 模块化设计,易于扩展和维护。
|
|
38
|
+
- 支持多租户功能。
|
|
39
|
+
- 提供详细的错误处理和日志记录。
|
|
40
|
+
|
|
16
41
|
### 示例
|
|
17
42
|
|
|
18
43
|
#### 示例代码
|
|
@@ -21,7 +46,24 @@ npm i --save @kne/fastify-user
|
|
|
21
46
|
|
|
22
47
|
### API
|
|
23
48
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
|
49
|
+
#### 账号管理
|
|
50
|
+
|
|
51
|
+
| 路径 | 方法 | 描述 | 参数 |
|
|
52
|
+
|------|------|------|------|
|
|
53
|
+
| `/api/account/v1.0.0/account/sendEmailCode` | POST | 发送登录邮箱验证码 | `email` (string), `type` (number), `options` (object) |
|
|
54
|
+
| `/api/account/v1.0.0/account/sendSMSCode` | POST | 发送登录短信验证码 | `phone` (string), `type` (number), `options` (object) |
|
|
55
|
+
| `/api/account/v1.0.0/account/validateCode` | POST | 验证码验证 | `name` (string), `type` (number), `code` (string) |
|
|
56
|
+
| `/api/account/v1.0.0/account/accountIsExists` | POST | 账号是否已存在 | `phone` (string) 或 `email` (string) |
|
|
57
|
+
| `/api/account/v1.0.0/account/register` | POST | 注册账号 | `phone` (string) 或 `email` (string), `password` (string), `code` (string), 其他可选字段 |
|
|
58
|
+
| `/api/account/v1.0.0/account/login` | POST | 登录 | `type` (string), `email` (string) 或 `phone` (string), `password` (string) |
|
|
59
|
+
| `/api/account/v1.0.0/account/modifyPassword` | POST | 修改密码 | `email` (string) 或 `phone` (string), `newPwd` (string), `oldPwd` (string) |
|
|
60
|
+
| `/api/account/v1.0.0/account/resetPassword` | POST | 重置密码 | `newPwd` (string), `token` (string) |
|
|
61
|
+
| `/api/account/v1.0.0/account/forgetPwd` | POST | 忘记密码 | `email` (string) 或 `phone` (string) |
|
|
62
|
+
| `/api/account/v1.0.0/account/parseResetToken` | POST | 通过token获取name | `token` (string) |
|
|
63
|
+
|
|
64
|
+
#### 用户信息管理
|
|
27
65
|
|
|
66
|
+
| 路径 | 方法 | 描述 | 参数 |
|
|
67
|
+
|------|------|------|------|
|
|
68
|
+
| `/api/account/v1.0.0/user/getUserInfo` | GET | 获取用户信息 | 无 |
|
|
69
|
+
| `/api/account/v1.0.0/user/saveUserInfo` | POST | 更新用户信息 | `avatar` (string), `nickname` (string), `email` (string), `phone` (string), `gender` (string), `birthday` (string), `description` (string) |
|
package/libs/models/user.js
CHANGED
|
@@ -2,58 +2,34 @@ const user = ({ DataTypes, definePrimaryType }) => {
|
|
|
2
2
|
return {
|
|
3
3
|
model: {
|
|
4
4
|
nickname: {
|
|
5
|
-
type: DataTypes.STRING,
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
type: DataTypes.STRING,
|
|
10
|
-
comment: '用户邮箱'
|
|
11
|
-
},
|
|
12
|
-
phone: {
|
|
13
|
-
type: DataTypes.STRING,
|
|
14
|
-
comment: '用户手机号'
|
|
15
|
-
},
|
|
16
|
-
userAccountId: definePrimaryType('userAccountId', {
|
|
17
|
-
allowNull: false,
|
|
18
|
-
comment: '当前账号id'
|
|
19
|
-
}),
|
|
20
|
-
status: {
|
|
21
|
-
type: DataTypes.INTEGER,
|
|
22
|
-
defaultValue: 0,
|
|
23
|
-
comment: '0:正常,10:初始化未激活,需要用户设置密码后使用,11:已禁用,12:已关闭'
|
|
24
|
-
},
|
|
25
|
-
avatar: {
|
|
26
|
-
type: DataTypes.STRING,
|
|
27
|
-
comment: '头像fileId'
|
|
28
|
-
},
|
|
29
|
-
gender: {
|
|
30
|
-
type: DataTypes.STRING,
|
|
31
|
-
comment: 'F:女,M:男'
|
|
32
|
-
},
|
|
33
|
-
birthday: {
|
|
34
|
-
type: DataTypes.DATE,
|
|
35
|
-
comment: '出生日期'
|
|
36
|
-
},
|
|
37
|
-
description: {
|
|
38
|
-
type: DataTypes.TEXT,
|
|
39
|
-
comment: '个人描述'
|
|
40
|
-
},
|
|
41
|
-
isSuperAdmin: {
|
|
42
|
-
type: DataTypes.BOOLEAN,
|
|
43
|
-
comment: '是否是平台超级管理员'
|
|
44
|
-
}
|
|
45
|
-
},
|
|
46
|
-
options: {
|
|
47
|
-
indexes: [
|
|
48
|
-
{
|
|
49
|
-
unique: true,
|
|
50
|
-
fields: ['email', 'deleted_at']
|
|
51
|
-
},
|
|
52
|
-
{
|
|
53
|
-
unique: true,
|
|
54
|
-
fields: ['phone', 'deleted_at']
|
|
5
|
+
type: DataTypes.STRING, comment: '用户昵称'
|
|
6
|
+
}, email: {
|
|
7
|
+
type: DataTypes.STRING, comment: '用户邮箱', set(value) {
|
|
8
|
+
this.setDataValue('email', value ? value.toLowerCase() : value);
|
|
55
9
|
}
|
|
56
|
-
|
|
10
|
+
}, phone: {
|
|
11
|
+
type: DataTypes.STRING, comment: '用户手机号'
|
|
12
|
+
}, userAccountId: definePrimaryType('userAccountId', {
|
|
13
|
+
allowNull: false, comment: '当前账号id'
|
|
14
|
+
}), status: {
|
|
15
|
+
type: DataTypes.INTEGER, defaultValue: 0, comment: '0:正常,10:初始化未激活,需要用户设置密码后使用,11:已禁用,12:已关闭'
|
|
16
|
+
}, avatar: {
|
|
17
|
+
type: DataTypes.STRING, comment: '头像fileId'
|
|
18
|
+
}, gender: {
|
|
19
|
+
type: DataTypes.STRING, comment: 'F:女,M:男'
|
|
20
|
+
}, birthday: {
|
|
21
|
+
type: DataTypes.DATE, comment: '出生日期'
|
|
22
|
+
}, description: {
|
|
23
|
+
type: DataTypes.TEXT, comment: '个人描述'
|
|
24
|
+
}, isSuperAdmin: {
|
|
25
|
+
type: DataTypes.BOOLEAN, comment: '是否是平台超级管理员'
|
|
26
|
+
}
|
|
27
|
+
}, options: {
|
|
28
|
+
indexes: [{
|
|
29
|
+
unique: true, fields: ['email', 'deleted_at']
|
|
30
|
+
}, {
|
|
31
|
+
unique: true, fields: ['phone', 'deleted_at']
|
|
32
|
+
}]
|
|
57
33
|
}
|
|
58
34
|
};
|
|
59
35
|
};
|
package/libs/services/account.js
CHANGED
package/libs/services/user.js
CHANGED
|
@@ -36,19 +36,17 @@ const userService = fp(async (fastify, options) => {
|
|
|
36
36
|
const accountIsExists = async ({ email, phone }, currentUser) => {
|
|
37
37
|
const query = [];
|
|
38
38
|
if (email && email !== get(currentUser, 'email')) {
|
|
39
|
-
query.push({ email });
|
|
39
|
+
query.push({ email: email.toLowerCase() });
|
|
40
40
|
}
|
|
41
41
|
if (phone && phone !== get(currentUser, 'phone')) {
|
|
42
42
|
query.push({ phone });
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
return (
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
})) > 0
|
|
51
|
-
);
|
|
45
|
+
return ((await models.user.count({
|
|
46
|
+
where: {
|
|
47
|
+
[Op.or]: query
|
|
48
|
+
}
|
|
49
|
+
})) > 0);
|
|
52
50
|
};
|
|
53
51
|
|
|
54
52
|
const addUser = async ({ avatar, nickname, gender, birthday, description, phone, email, password, status }) => {
|
|
@@ -60,15 +58,7 @@ const userService = fp(async (fastify, options) => {
|
|
|
60
58
|
}
|
|
61
59
|
const account = await models.userAccount.create(await services.account.passwordEncryption(password));
|
|
62
60
|
const user = await models.user.create({
|
|
63
|
-
avatar,
|
|
64
|
-
nickname,
|
|
65
|
-
gender,
|
|
66
|
-
birthday,
|
|
67
|
-
description,
|
|
68
|
-
phone,
|
|
69
|
-
email,
|
|
70
|
-
status,
|
|
71
|
-
userAccountId: account.id
|
|
61
|
+
avatar, nickname, gender, birthday, description, phone, email, status, userAccountId: account.id
|
|
72
62
|
});
|
|
73
63
|
await account.update({ belongToUserId: user.id });
|
|
74
64
|
|
|
@@ -78,28 +68,43 @@ const userService = fp(async (fastify, options) => {
|
|
|
78
68
|
const getUserList = async ({ filter, perPage, currentPage }) => {
|
|
79
69
|
const queryFilter = {};
|
|
80
70
|
|
|
81
|
-
['nickname'].forEach(key => {
|
|
71
|
+
['nickname', 'phone', 'email'].forEach(key => {
|
|
82
72
|
if (filter && filter[key]) {
|
|
83
73
|
queryFilter[key] = {
|
|
84
74
|
[Op.like]: `%${filter[key]}%`
|
|
85
75
|
};
|
|
86
76
|
}
|
|
87
77
|
});
|
|
78
|
+
['status'].forEach(key => {
|
|
79
|
+
if (filter && filter[key]) {
|
|
80
|
+
queryFilter[key] = filter[key];
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
if (filter?.isSuperAdmin !== undefined) {
|
|
84
|
+
if (filter.isSuperAdmin === 'true') {
|
|
85
|
+
queryFilter.isSuperAdmin = true;
|
|
86
|
+
} else {
|
|
87
|
+
queryFilter.isSuperAdmin = {
|
|
88
|
+
[Op.or]: [false, null]
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
88
93
|
const { count, rows } = await models.user.findAndCountAll({
|
|
89
94
|
where: queryFilter,
|
|
90
95
|
offset: perPage * (currentPage - 1),
|
|
91
|
-
limit: perPage
|
|
96
|
+
limit: perPage,
|
|
97
|
+
order: [['createdAt', 'DESC']]
|
|
92
98
|
});
|
|
93
99
|
return {
|
|
94
100
|
pageData: rows.map(item => {
|
|
95
101
|
return Object.assign({}, item.get({ pain: true }));
|
|
96
|
-
}),
|
|
97
|
-
totalCount: count
|
|
102
|
+
}), totalCount: count
|
|
98
103
|
};
|
|
99
104
|
};
|
|
100
105
|
|
|
101
106
|
const saveUser = async ({ id, ...otherInfo }) => {
|
|
102
|
-
const user = await getUserInstance({id});
|
|
107
|
+
const user = await getUserInstance({ id });
|
|
103
108
|
|
|
104
109
|
if ((await accountIsExists({ phone: otherInfo.phone, email: otherInfo.email }, user)) > 0) {
|
|
105
110
|
throw new Error('手机号或者邮箱都不能重复');
|
|
@@ -127,14 +132,7 @@ const userService = fp(async (fastify, options) => {
|
|
|
127
132
|
};
|
|
128
133
|
|
|
129
134
|
services.user = {
|
|
130
|
-
getUserInstance,
|
|
131
|
-
getUser,
|
|
132
|
-
addUser,
|
|
133
|
-
saveUser,
|
|
134
|
-
accountIsExists,
|
|
135
|
-
getUserList,
|
|
136
|
-
setSuperAdmin,
|
|
137
|
-
setUserStatus
|
|
135
|
+
getUserInstance, getUser, addUser, saveUser, accountIsExists, getUserList, setSuperAdmin, setUserStatus
|
|
138
136
|
};
|
|
139
137
|
});
|
|
140
138
|
|