create-pixle-koa-template 1.0.0

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.
Files changed (43) hide show
  1. package/bin/create-app.js +76 -0
  2. package/package.json +17 -0
  3. package/template/.env +20 -0
  4. package/template/app.js +49 -0
  5. package/template/config/db.js +29 -0
  6. package/template/config/email.js +30 -0
  7. package/template/config/index.js +36 -0
  8. package/template/config/redis.js +19 -0
  9. package/template/controllers/AuthController.js +71 -0
  10. package/template/controllers/DownloadController.js +18 -0
  11. package/template/controllers/UploadController.js +60 -0
  12. package/template/controllers/UserController.js +90 -0
  13. package/template/middleware/auth.js +91 -0
  14. package/template/middleware/errorHandler.js +17 -0
  15. package/template/middleware/logger.js +41 -0
  16. package/template/middleware/notFound.js +84 -0
  17. package/template/middleware/upload.js +165 -0
  18. package/template/models/Auth.js +11 -0
  19. package/template/models/BaseDAO.js +449 -0
  20. package/template/models/User.js +10 -0
  21. package/template/package-lock.json +3427 -0
  22. package/template/package.json +34 -0
  23. package/template/public/404.html +160 -0
  24. package/template/routes/auth.js +21 -0
  25. package/template/routes/download.js +9 -0
  26. package/template/routes/index.js +105 -0
  27. package/template/routes/upload.js +28 -0
  28. package/template/routes/user.js +22 -0
  29. package/template/services/AuthService.js +190 -0
  30. package/template/services/CodeRedisService.js +94 -0
  31. package/template/services/DownloadService.js +54 -0
  32. package/template/services/EmailService.js +245 -0
  33. package/template/services/JwtTokenService.js +50 -0
  34. package/template/services/PasswordService.js +133 -0
  35. package/template/services/TokenRedisService.js +29 -0
  36. package/template/services/UserService.js +128 -0
  37. package/template/utils/crypto.js +9 -0
  38. package/template/utils/passwordValidator.js +81 -0
  39. package/template/utils/prototype/day.js +237 -0
  40. package/template/utils/prototype/deepClone.js +32 -0
  41. package/template/utils/prototype/index.js +61 -0
  42. package/template/utils/response.js +26 -0
  43. package/template//344/275/277/347/224/250/346/225/231/347/250/213.md +881 -0
@@ -0,0 +1,54 @@
1
+ /**
2
+ * 下载服务
3
+ */
4
+ const { error } = require("../utils/response.js");
5
+ const send = require("koa-send");
6
+ const path = require("path");
7
+
8
+
9
+ class DownloadService {
10
+ /**
11
+ * 文件下载服务
12
+ * @param {Object} ctx - Koa上下文对象,包含请求和响应信息
13
+ * @param {string} url - 要下载的文件相对路径(如:storage/uploads/image/2025/12/16/file.png)
14
+ * @returns {Promise<void>} - 无返回值,直接通过ctx返回文件流或错误响应
15
+ */
16
+ async download(ctx, url) {
17
+ try {
18
+ // 将URL路径按"/"分割成数组,用于解析目录和文件名
19
+ let urlArr = url.split("/");
20
+
21
+ // 获取数组最后一个元素作为文件名
22
+ const fileName = urlArr[urlArr.length - 1];
23
+
24
+ // 获取除了最后一个元素外的所有部分,重新拼接成目录路径
25
+ const fileDir = urlArr.slice(0, urlArr.length - 1).join("/");
26
+
27
+ // 构建完整的本地文件目录路径
28
+ // __dirname 表示当前文件所在目录
29
+ // 向上跳转一级(../)后拼接上文件目录
30
+ const dirPath = path.join(__dirname, `../${fileDir}`);
31
+
32
+ // 调用 koa-send 中间件实现文件下载
33
+ let result = await send(ctx, fileName, {
34
+ root: dirPath, // 指定文件所在的根目录
35
+ setHeaders: (res) => {
36
+ // 设置 Content-Disposition 响应头为 attachment,强制浏览器下载文件
37
+ // encodeURIComponent 确保文件名中的特殊字符能正确处理
38
+ res.setHeader(
39
+ "Content-Disposition",
40
+ `attachment; filename="${encodeURIComponent(fileName)}"`
41
+ );
42
+ },
43
+ });
44
+
45
+ } catch (err) {
46
+ // 捕获并处理下载过程中的错误
47
+ // 如果是文件不存在等404错误,返回404状态码
48
+ // 其他错误返回相应的错误状态码
49
+ error(ctx, "文件不存在", err.status || 404);
50
+ }
51
+ }
52
+ }
53
+
54
+ module.exports = new DownloadService();
@@ -0,0 +1,245 @@
1
+ /**
2
+ * 验证码发送工具
3
+ */
4
+
5
+ const nodemailer = require("nodemailer");
6
+ // 引入QQ邮箱配置
7
+ const QQ_EMAIL_CONFIG = require("../config/email");
8
+
9
+ // 创建传输器
10
+ const transporter = nodemailer.createTransport(QQ_EMAIL_CONFIG);
11
+
12
+ /**
13
+ * 生成4位验证码
14
+ * @returns 验证码
15
+ */
16
+ const generateVerificationCode = () => {
17
+ let code = "";
18
+ for (let i = 0; i < 4; i++) {
19
+ code += Math.floor(Math.random() * 10); // 0-9
20
+ }
21
+ return code;
22
+ };
23
+
24
+ /**
25
+ * 获取邮箱验证码主题
26
+ * @param {*} type :验证码类型: register:注册, resetPassword:重置密码
27
+ * @returns 验证码主题
28
+ */
29
+ const getEmailSubject = (type = "register") => {
30
+ let subjectOptions = [];
31
+ switch (type) {
32
+ case "register":
33
+ subjectOptions = ["注册", "注册账号"];
34
+ break;
35
+ case "resetPassword":
36
+ subjectOptions = ["重置密码", "重置密码"];
37
+ break;
38
+ default:
39
+ subjectOptions = ["注册", "注册账号"];
40
+ }
41
+ return subjectOptions;
42
+ };
43
+
44
+ /**
45
+ * 发送验证码邮件
46
+ * @param {*} email 收件人邮箱
47
+ * @param {*} code 验证码
48
+ * @param {*} type :验证码类型: register:注册, resetPassword:重置密码
49
+ * @returns
50
+ */
51
+ const sendVerificationCode = async (email, code, type = "register") => {
52
+ try {
53
+ // 验证收件人邮箱格式
54
+ if (!isValidEmail(email)) {
55
+ throw new Error("收件人邮箱格式不正确,请输入正确的邮箱");
56
+ }
57
+
58
+ const subjectOptions = getEmailSubject(type);
59
+
60
+
61
+ const mailOptions = {
62
+ from: {
63
+ name: "验证码服务", // 发件人名称
64
+ address: QQ_EMAIL_CONFIG.auth.user,
65
+ },
66
+ to: email,
67
+ subject: `【重要】${subjectOptions[0]}验证码`,
68
+ text: `您的${subjectOptions[0]}验证码是:${code},验证码5分钟内有效。`,
69
+ html: generateQQEmailTemplate(code,subjectOptions),
70
+ };
71
+
72
+ // 发送邮件
73
+ const info = await transporter.sendMail(mailOptions);
74
+
75
+ return {
76
+ success: true,
77
+ message: "验证码发送成功",
78
+ messageId: info.messageId,
79
+ to: email,
80
+ };
81
+ } catch (error) {
82
+ // console.error(" 发送验证码失败:", error.message);
83
+ // 提供友好的错误提示
84
+ let userMessage = "验证码发送失败";
85
+ if (error.code === "EAUTH") {
86
+ userMessage = "邮箱认证失败,请检查授权码是否正确";
87
+ } else if (error.code === "ECONNECTION") {
88
+ userMessage = "连接QQ邮箱服务器失败,请检查网络";
89
+ } else if (error.responseCode === 550) {
90
+ userMessage = "收件人邮箱地址不存在或无法接收邮件";
91
+ }
92
+
93
+ throw new Error(userMessage);
94
+ }
95
+ };
96
+
97
+ // 验证是否为邮箱
98
+ const isValidEmail = (email) => {
99
+ const reg = /^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/;
100
+ return reg.test(email);
101
+ };
102
+
103
+ // 生成QQ邮箱专用HTML模板
104
+ const generateQQEmailTemplate = (code,subjectOptions) => {
105
+ return `
106
+ <!DOCTYPE html>
107
+ <html lang="zh-CN">
108
+ <head>
109
+ <meta charset="UTF-8">
110
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
111
+ <title>${subjectOptions[0]}验证码</title>
112
+ <style>
113
+ body {
114
+ font-family: 'Microsoft YaHei', Arial, sans-serif;
115
+ line-height: 1.6;
116
+ color: #333;
117
+ margin: 0;
118
+ padding: 20px;
119
+ background-color: #f5f5f5;
120
+ }
121
+ .email-container {
122
+ max-width: 600px;
123
+ margin: 0 auto;
124
+ background-color: white;
125
+ border-radius: 8px;
126
+ overflow: hidden;
127
+ box-shadow: 0 2px 10px rgba(0,0,0,0.1);
128
+ }
129
+ .header {
130
+ background: linear-gradient(135deg, #12c2e9, #c471ed, #f64f59);
131
+ color: white;
132
+ padding: 30px 20px;
133
+ text-align: center;
134
+ }
135
+ .header h1 {
136
+ margin: 0;
137
+ font-size: 24px;
138
+ }
139
+ .content {
140
+ padding: 30px;
141
+ }
142
+ .code-container {
143
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
144
+ color: white;
145
+ padding: 25px;
146
+ text-align: center;
147
+ border-radius: 8px;
148
+ margin: 30px 0;
149
+ box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3);
150
+ }
151
+ .code {
152
+ font-size: 42px;
153
+ font-weight: bold;
154
+ letter-spacing: 8px;
155
+ text-shadow: 2px 2px 4px rgba(0,0,0,0.2);
156
+ font-family: 'Courier New', monospace;
157
+ }
158
+ .tips {
159
+ background-color: #f8f9fa;
160
+ border-left: 4px solid #12c2e9;
161
+ padding: 15px;
162
+ margin: 20px 0;
163
+ border-radius: 4px;
164
+ }
165
+ .footer {
166
+ text-align: center;
167
+ padding: 20px;
168
+ color: #666;
169
+ font-size: 12px;
170
+ border-top: 1px solid #eee;
171
+ background-color: #f9f9f9;
172
+ }
173
+ .warning {
174
+ color: #ff6b6b;
175
+ font-weight: bold;
176
+ background-color: #ffeaa7;
177
+ padding: 10px;
178
+ border-radius: 4px;
179
+ margin: 15px 0;
180
+ }
181
+ .qq-logo {
182
+ width: 60px;
183
+ height: 60px;
184
+ background: linear-gradient(135deg, #12c2e9, #c471ed);
185
+ border-radius: 50%;
186
+ display: flex;
187
+ align-items: center;
188
+ justify-content: center;
189
+ margin: 0 auto 20px;
190
+ color: white;
191
+ font-size: 24px;
192
+ font-weight: bold;
193
+ }
194
+ </style>
195
+ </head>
196
+ <body>
197
+ <div class="email-container">
198
+ <div class="header">
199
+ <div class="qq-logo">Q</div>
200
+ <h1>邮箱验证码</h1>
201
+ <p>邮箱安全验证服务</p>
202
+ </div>
203
+
204
+ <div class="content">
205
+ <p>尊敬的用户 ,您好!</p>
206
+ <p>您正在申请${subjectOptions[1]},请使用以下验证码完成验证:</p>
207
+
208
+ <div class="code-container">
209
+ <div class="code">${code}</div>
210
+ </div>
211
+
212
+ <div class="warning">
213
+ ⚠️ 重要提示:请勿将验证码透露给任何人!
214
+ </div>
215
+
216
+ <div class="tips">
217
+ <p><strong>使用说明:</strong></p>
218
+ <ul>
219
+ <li>此验证码有效期为 <strong>5分钟</strong></li>
220
+ <li>请在${subjectOptions[0]}页面输入此验证码完成验证</li>
221
+ <li>如非本人操作,请忽略此邮件</li>
222
+ <li>如有疑问,请联系客服</li>
223
+ </ul>
224
+ </div>
225
+
226
+ <p>感谢您使用我们的服务!</p>
227
+ <p><em>系统自动发送,请勿回复本邮件。</em></p>
228
+ </div>
229
+
230
+ <div class="footer">
231
+ <p>© ${new Date().getFullYear()} 邮箱验证服务</p>
232
+ <p>本邮件由系统自动发送,如有疑问请联系管理员</p>
233
+ <p>发送时间:${new Date().toLocaleString("zh-CN")}</p>
234
+ </div>
235
+ </div>
236
+ </body>
237
+ </html>
238
+ `;
239
+ };
240
+
241
+ module.exports = {
242
+ generateVerificationCode,
243
+ sendVerificationCode,
244
+ isValidEmail,
245
+ };
@@ -0,0 +1,50 @@
1
+ /**
2
+ * jwt token服务(生成、验证、解码)
3
+ */
4
+
5
+ const jwt = require("jsonwebtoken");
6
+ const config = require("../config");
7
+ const { setToken} = require("./TokenRedisService.js");
8
+
9
+ /**
10
+ * 生成token
11
+ * @param {*} payload: 用户信息
12
+ * @param {*} options : 配置项
13
+ * @returns token
14
+ */
15
+ const generateToken = async (payload = { account: "", userId: "" }) => {
16
+ let token = jwt.sign(payload, config.jwt.secret, {
17
+ expiresIn: config.jwt.expiresIn,
18
+ });
19
+
20
+ //保存token到redis
21
+ await setToken(payload.userId,token);
22
+ return token;
23
+ };
24
+
25
+ /**
26
+ * 验证token
27
+ * @param {*} token
28
+ * @returns
29
+ */
30
+ const verifyToken = (token) => {
31
+ try {
32
+ return jwt.verify(token, config.jwt.secret);
33
+ } catch (error) {
34
+ throw new Error("Token验证失败");
35
+ }
36
+ };
37
+ /**
38
+ * 解码token
39
+ * @param {*} token
40
+ * @returns
41
+ */
42
+ const decodeToken = (token) => {
43
+ try {
44
+ return jwt.decode(token, config.jwt.secret);
45
+ } catch (error) {
46
+ throw new Error("Token解码失败");
47
+ }
48
+ };
49
+
50
+ module.exports = { generateToken, verifyToken, decodeToken };
@@ -0,0 +1,133 @@
1
+ /**
2
+ * 密码服务
3
+ */
4
+
5
+ const authDAO = require("../models/Auth.js");
6
+ const { error } = require("../utils/response.js");
7
+ const { deleteToken } = require("./TokenRedisService.js");
8
+ const argon2 = require("argon2");
9
+ const passwordValidator = require("../utils/passwordValidator.js");
10
+ const {
11
+ isValidEmail,
12
+ } = require("./EmailService.js");
13
+ const CodeRedisService = require("./CodeRedisService.js");
14
+ const codeRedisService = new CodeRedisService("resetPassword");
15
+
16
+
17
+ class PasswordService {
18
+ /**
19
+ * 修改密码
20
+ * @param {*} ctx 上下文对象
21
+ * @param {*} oldPassword 旧密码
22
+ * @param {*} newPassword 新密码
23
+ * @returns {boolean|undefined} 是否修改成功
24
+ */
25
+ async changePassword(ctx, oldPassword, newPassword) {
26
+ // 从上下文状态中获取当前登录用户的ID
27
+ let { userId } = ctx.state.user;
28
+
29
+ // 根据用户ID从数据库中查找用户信息
30
+ let result = await authDAO.findById(userId);
31
+
32
+ // 检查用户是否存在
33
+ if (!result) return error(ctx, "用户不存在", 400);
34
+
35
+ // 提取用户ID和数据库中存储的哈希密码
36
+ const { id, password: hashedPassword } = result;
37
+
38
+ // 使用argon2验证旧密码的正确性
39
+ let isMatch = await argon2.verify(hashedPassword, oldPassword);
40
+
41
+ // 如果旧密码验证失败,返回错误响应
42
+ if (!isMatch) {
43
+ return error(ctx, "旧密码错误", 400);
44
+ }
45
+
46
+ //新密码强度验证
47
+ let validateResult = passwordValidator.validate(newPassword);
48
+ if (!validateResult.isValid) {
49
+ return error(ctx, validateResult.errors[0], 400);
50
+ }
51
+
52
+ // 使用argon2对新密码进行加密,配置参数:
53
+ // - type: 使用argon2id算法(推荐的平衡安全性和性能的算法)
54
+ // - memoryCost: 内存成本设为2^16(64MB)
55
+ // - timeCost: 时间成本设为3(迭代次数)
56
+ // - parallelism: 并行度设为1(线程数)
57
+ const newHashedPassword = await argon2.hash(newPassword, {
58
+ type: argon2.argon2id,
59
+ memoryCost: 2 ** 16,
60
+ timeCost: 3,
61
+ parallelism: 1,
62
+ });
63
+
64
+ // 更新数据库中的用户密码
65
+ await authDAO.update(id, {
66
+ password: newHashedPassword,
67
+ });
68
+
69
+ // 密码修改成功后,删除用户的token,确保旧密码的token失效
70
+ await deleteToken(userId);
71
+
72
+ // 返回成功标志
73
+ return true;
74
+ }
75
+
76
+ /**
77
+ * 重置密码
78
+ * @param {*} ctx 上下文对象
79
+ * @param {*} email 邮箱
80
+ * @param {*} code 验证码
81
+ * @param {*} password 新密码
82
+ * @returns {boolean|undefined} 是否重置成功
83
+ */
84
+ async resetPassword(ctx, email, code, password) {
85
+ //邮箱格式校验
86
+ if (!isValidEmail(email)) {
87
+ return error(ctx, "邮箱格式不正确", 400);
88
+ }
89
+
90
+ //新密码强度验证
91
+ let validateResult = passwordValidator.validate(password);
92
+ if (!validateResult.isValid) {
93
+ return error(ctx, validateResult.errors[0], 400);
94
+ }
95
+
96
+ // 从数据库中查找用户信息
97
+ let result = await authDAO.findOne({ email });
98
+ // 检查用户是否存在
99
+ if (!result) return error(ctx, "用户不存在", 400);
100
+
101
+ const storedCode = await codeRedisService.getVerifyCode(email);
102
+ if (!storedCode) {
103
+ return error(ctx, "验证码无效或已过期,请重新获取", 400);
104
+ }
105
+
106
+ if (storedCode !== code) {
107
+ return error(ctx, "验证码错误,请重新输入", 400);
108
+ }
109
+ // 验证成功后清理 - 防止重复使用
110
+ await codeRedisService.delVerifyCode(email);
111
+
112
+ // 使用argon2对新密码进行加密
113
+ const newHashedPassword = await argon2.hash(password, {
114
+ type: argon2.argon2id,
115
+ memoryCost: 2 ** 16,
116
+ timeCost: 3,
117
+ parallelism: 1,
118
+ });
119
+
120
+ // 更新数据库中的用户密码
121
+ await authDAO.update(result.id, {
122
+ password: newHashedPassword,
123
+ });
124
+
125
+ // 密码重置成功后,删除用户的token,确保旧密码的token失效
126
+ await deleteToken(result.id);
127
+ return true;
128
+ }
129
+
130
+
131
+ }
132
+
133
+ module.exports = new PasswordService();
@@ -0,0 +1,29 @@
1
+ /**
2
+ * token存储redis服务
3
+ */
4
+ const redis = require("../config/redis");
5
+ const config = require("../config/index.js");
6
+ /**
7
+ * 存储token到白名单
8
+ * @param {*} token: token
9
+ * @param {*} userId: 用户id
10
+ * @param {*} expire: 过期时间,默认7天
11
+ */
12
+ const setToken = async (
13
+ userId,
14
+ token,
15
+ expire = config.jwt.expiresIn || 24 * 60 * 60 * 7
16
+ ) => {
17
+ return await redis.set(`whitelist:userid:${userId}`, token, "EX", expire);
18
+ };
19
+
20
+ // 从白名单获取token(传入对应userId返回token,不存在则为 null)
21
+ const getToken = async (userId) => {
22
+ return await redis.get(`whitelist:userid:${userId}`);
23
+ };
24
+ // 从白名单中删除 token(登出时用)
25
+ const deleteToken = async (userId) => {
26
+ return await redis.del(`whitelist:userid:${userId}`);
27
+ };
28
+
29
+ module.exports = { setToken, getToken, deleteToken };
@@ -0,0 +1,128 @@
1
+ /**
2
+ * 用户服务
3
+ */
4
+
5
+ const userDAO = require("../models/User.js");
6
+ const { error } = require("../utils/response.js");
7
+ const dayjs = require('dayjs');
8
+
9
+ class UserService {
10
+ //用户详情
11
+ async getUserById(ctx, userId) {
12
+ // 通过用户ID从数据库查询用户信息
13
+ let result = await userDAO.findById(userId);
14
+ // 如果查询结果为空,返回用户不存在的错误信息
15
+ if (!result) {
16
+ return error(ctx, "用户不存在", 400);
17
+ } else {
18
+ // 使用对象解构过滤掉密码字段,确保密码不会返回给客户端
19
+ let { password, ...data } = result;
20
+
21
+ // 返回过滤后的用户数据(不包含密码)
22
+ return {
23
+ ...data,
24
+ created_at: dayjs(data.created_at).format('YYYY-MM-DD HH:mm:ss'),
25
+ updated_at: dayjs(data.updated_at).format('YYYY-MM-DD HH:mm:ss')
26
+ };
27
+ }
28
+ }
29
+
30
+
31
+ //更新用户
32
+ async updateUser(ctx, userId, params) {
33
+ // 调用数据访问层更新用户信息
34
+ let result = await userDAO.update(userId, params);
35
+
36
+ // 如果更新成功返回更新结果,否则返回用户不存在的错误信息
37
+ return result || error(ctx, "用户不存在", 400);
38
+ }
39
+
40
+ //删除用户
41
+ async deleteUser(ctx, userId) {
42
+ // 调用数据访问层删除用户
43
+ let result = await userDAO.delete(userId);
44
+
45
+ // 如果删除成功返回删除结果,否则返回用户不存在的错误信息
46
+ return result || error(ctx, "用户不存在", 400);
47
+ }
48
+
49
+ //获取用户列表
50
+ async getUserList(ctx) {
51
+ // 定义字段类型映射,all字段为布尔类型
52
+ const fieldTypes = { all: "boolean" };
53
+
54
+ // 参数类型转换
55
+ let {
56
+ all = false, // 是否查询所有数据,默认为false(分页查询)
57
+ startTime, // 开始时间
58
+ endTime, // 结束时间
59
+ email, // 邮箱(模糊查询)
60
+ page, // 页码
61
+ pageSize, // 每页大小
62
+ } = userDAO.convertQueryParams(ctx.request.query, fieldTypes);
63
+
64
+ // 查询条件
65
+ let conditions = {};
66
+
67
+ // 邮箱模糊查询条件
68
+ if (email) {
69
+ conditions["$like_email"] = email;
70
+ }
71
+
72
+ // 时间范围查询条件
73
+ if (startTime && endTime) {
74
+ // 时间区间查询
75
+ conditions["$between_created_at"] = [
76
+ startTime,
77
+ endTime,
78
+ ];
79
+ } else if (startTime && !endTime) {
80
+ // 大于等于开始时间
81
+ conditions["$gte_created_at"] = startTime;
82
+ } else if (endTime && !startTime) {
83
+ // 小于等于结束时间
84
+ conditions["$lte_created_at"] = endTime;
85
+ }
86
+
87
+ // 查询所有用户(不分页)
88
+ if (all) {
89
+ let result = await userDAO.findAll(conditions, {}, fieldTypes);
90
+
91
+ // 格式化时间字段
92
+ result = result.map((item) => {
93
+ // 使用对象解构过滤掉密码字段,确保密码不会返回给客户端
94
+ let { password, ...data } = item;
95
+ return {
96
+ ...data,
97
+ created_at: dayjs(item.created_at).format('YYYY-MM-DD HH:mm:ss'),
98
+ updated_at: dayjs(item.updated_at).format('YYYY-MM-DD HH:mm:ss')
99
+ };
100
+ });
101
+ return result;
102
+ }
103
+ // 分页查询用户
104
+ else {
105
+ let result = await userDAO.paginate(
106
+ page,
107
+ pageSize,
108
+ conditions,
109
+ {},
110
+ fieldTypes
111
+ );
112
+
113
+ // 格式化时间字段
114
+ result.list = result.list.map((item) => {
115
+ // 使用对象解构过滤掉密码字段,确保密码不会返回给客户端
116
+ let { password, ...data } = item;
117
+ return {
118
+ ...data,
119
+ created_at: dayjs(item.created_at).format('YYYY-MM-DD HH:mm:ss'),
120
+ updated_at: dayjs(item.updated_at).format('YYYY-MM-DD HH:mm:ss')
121
+ };
122
+ });
123
+ return result;
124
+ }
125
+ }
126
+ }
127
+
128
+ module.exports = new UserService();
@@ -0,0 +1,9 @@
1
+ const crypto = require('crypto');
2
+
3
+ // 生成 64 字节的 Base64 编码密钥
4
+ const generateJWTSecret = () => {
5
+ let secret=crypto.randomBytes(64).toString('base64');
6
+ return secret
7
+ };
8
+
9
+ module.exports = { generateJWTSecret };