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,84 @@
1
+ const path = require('path');
2
+ const fs = require('fs');
3
+
4
+ /**
5
+ * 404 405中间件
6
+ * 处理404 405错误,返回自定义的JSON响应或HTML页面
7
+ */
8
+
9
+ const notFoundMiddleware = async (ctx, next) => {
10
+
11
+ // 先执行所有其他中间件
12
+ await next();
13
+
14
+ // 如果已经有响应体,直接返回
15
+ if (ctx.body) {
16
+ return;
17
+ }
18
+
19
+ // 检查是否为405错误:路径存在但请求方法不允许
20
+ let isMethodNotAllowed = false;
21
+ let allowedMethods = [];
22
+ let pathExists = false;
23
+
24
+ if (ctx.app.context.registeredRoutes) {
25
+ const requestedPath = ctx.path;
26
+ const requestedMethod = ctx.method;
27
+
28
+ // 遍历所有注册的路由
29
+ for (const route of ctx.app.context.registeredRoutes) {
30
+ // 检查路径是否匹配
31
+ if (route.regexp && route.regexp.test(requestedPath)) {
32
+ pathExists = true;
33
+
34
+ // 如果路径匹配但方法不允许,收集允许的方法
35
+ if (!route.methods.includes(requestedMethod) && !route.methods.includes('HEAD')) {
36
+ // 收集允许的方法(过滤掉HEAD方法,因为它通常是自动支持的)
37
+ allowedMethods = [...new Set([...allowedMethods, ...route.methods.filter(m => m !== 'HEAD')])];
38
+ } else {
39
+ // 如果请求方法是允许的,则不是405错误
40
+ isMethodNotAllowed = false;
41
+ break;
42
+ }
43
+ }
44
+ }
45
+
46
+ // 只有当路径存在且允许的方法不为空时,才是405错误
47
+ isMethodNotAllowed = pathExists && allowedMethods.length > 0;
48
+ }
49
+
50
+
51
+ // 处理405错误
52
+ if (isMethodNotAllowed) {
53
+ ctx.status = 405;
54
+ ctx.set('Allow', allowedMethods.join(', '));
55
+ ctx.type = 'application/json';
56
+ ctx.body = {
57
+ code: 405,
58
+ message: '请求方法不允许'
59
+ };
60
+ }
61
+ // 处理404错误
62
+ else {
63
+ // 无论当前状态码是什么,对于不存在的路径都设置为404
64
+ ctx.status = 404;
65
+
66
+ // 检查是否为静态资源请求
67
+ const isStaticResource = ctx.path.startsWith('/static/') ||
68
+ /\.(jpg|jpeg|png|gif|svg|css|js|html|ico|woff|woff2|ttf|eot)$/i.test(ctx.path);
69
+
70
+ if (isStaticResource) {
71
+ ctx.type = 'text/html';
72
+ ctx.body = fs.readFileSync(path.join(__dirname, '../public/404.html'), 'utf8');
73
+ } else {
74
+ ctx.type = 'application/json';
75
+ ctx.body = {
76
+ code: 404,
77
+ message: 'request not find'
78
+ };
79
+ }
80
+ }
81
+
82
+ };
83
+
84
+ module.exports = notFoundMiddleware;
@@ -0,0 +1,165 @@
1
+ const multer = require("@koa/multer");
2
+ const path = require("path");
3
+ const fs = require("fs");
4
+ const dayjs = require("dayjs");
5
+ const {upload:uploadConfig}=require('../config/index')
6
+ // 上传配置
7
+ const {uploadDir,maxSize,maxCount} = uploadConfig
8
+
9
+
10
+ // 递归创建存储目录
11
+ const mkdir = (dirPath) => {
12
+ if (!fs.existsSync(dirPath)) {
13
+ fs.mkdirSync(dirPath, { recursive: true });
14
+ }
15
+ };
16
+
17
+ /**
18
+ * 上传中间件主函数
19
+ *入参可选配置(对象):
20
+ * @param {*} uploadPath :上传文件路径(可选)
21
+ * @param {*} limits :上传文件大小、个数限制可选配置,默认20MB、一次最多传9个文件 (可选)
22
+ * limits:{
23
+ fileSize: 20 * 1024 * 1024, // 单个文件最大20MB
24
+ files: 9, //一次最多9个文件
25
+ }
26
+ * @param {*} allowedMimes :允许文件类型,默认全部允许 (可选),数组例如:["image/jpeg", "image/png", "image/gif", "image/webp"]
27
+ * @param {*} fileFilter :自定义过滤函数 (可选)
28
+ * @returns
29
+ */
30
+ const uploadMiddleware = ({
31
+ uploadPath = null,
32
+ limits = {},
33
+ allowedMimes = null,
34
+ fileFilter = null,
35
+ } = {}) => {
36
+ // 配置存储引擎
37
+ const storage = multer.diskStorage({
38
+ destination: (req, file, cb) => {
39
+ // 通过 MIME 类型检测是图片类型还是其他文件
40
+ const targetDir =
41
+ uploadPath || file.mimetype.startsWith("image/")
42
+ ? uploadDir.image
43
+ : uploadDir.file;
44
+ //上传目录
45
+ let realUploadDir = path.join(
46
+ targetDir,
47
+ `${dayjs().format("YYYY/MM/DD")}`
48
+ );
49
+ //创建日期目录
50
+ mkdir(realUploadDir);
51
+
52
+ cb(null, realUploadDir);
53
+ },
54
+ filename: (req, file, cb) => {
55
+ //文件名前缀
56
+ let prefix=file.mimetype.startsWith("image/") ? "image" : "file";
57
+ // 生成唯一文件名
58
+ const fileExt = path.extname(file.originalname);
59
+ //格式:image或file+ 时间戳 + 随机数 + 文件扩展名
60
+ const fileName = `${prefix}_${Date.now()}_${Math.random()
61
+ .toString(36)
62
+ .substring(2)}${fileExt}`;
63
+ cb(null, fileName);
64
+ },
65
+ });
66
+
67
+ // 自定义文件过滤器
68
+ const _fileFilter = (req, file, cb) => {
69
+ // 如果传入了自定义过滤器,使用自定义的
70
+ if (fileFilter) {
71
+ return fileFilter(req, file, cb);
72
+ }
73
+
74
+ // 如果指定了允许的 MIME 类型
75
+ if (Array.isArray(allowedMimes) && allowedMimes.length > 0) {
76
+ if (allowedMimes.includes(file.mimetype)) {
77
+ cb(null, true);
78
+ } else {
79
+ // 生成友好的错误信息
80
+ const allowedTypes = allowedMimes.map(item => {
81
+ const parts = item.split('/');
82
+ return parts.length > 1 ? parts[1] : parts[0];
83
+ });
84
+
85
+ cb(
86
+ new Error(`仅支持 ${allowedTypes.join('、')} 格式的文件`),
87
+ false
88
+ );
89
+ }
90
+ } else {
91
+ // 没有限制,允许所有文件
92
+ cb(null, true);
93
+ }
94
+ };
95
+
96
+ // 创建 multer 实例
97
+ const multerUpload = multer({
98
+ storage,
99
+ limits: {
100
+ fileSize: maxSize, // 大小 限制
101
+ files: maxCount, //最多9个文件
102
+ ...limits,
103
+ },
104
+ fileFilter: _fileFilter,
105
+ });
106
+
107
+ // 错误处理函数
108
+ const handleMulterError = (err, ctx) => {
109
+ const multerFileErrors = [
110
+ { key: "LIMIT_FILE_SIZE", message: "文件大小超限" },
111
+ { key: "LIMIT_FILE_COUNT", message: "文件数量超限" },
112
+ { key: "LIMIT_UNEXPECTED_FILE", message: "意外的文件字段" },
113
+ { key: "LIMIT_FIELD_KEY", message: "字段名过长" },
114
+ { key: "LIMIT_FIELD_VALUE", message: "字段值过长" },
115
+ { key: "LIMIT_FIELD_COUNT", message: "字段数量超限" },
116
+ { key: "LIMIT_PART_COUNT", message: "表单字段数量超限" },
117
+ ];
118
+
119
+ const target =
120
+ err.code && multerFileErrors.find((item) => item.key === err.code);
121
+
122
+ ctx.status = 400;
123
+ ctx.body = {
124
+ code: 400,
125
+ message: target?.message ?? err.message ?? "上传失败",
126
+ };
127
+ };
128
+
129
+ // 适配函数 - 使用 try-catch
130
+ const handleUpload = (type, fieldName, fields = []) => {
131
+ let uploadMiddleware;
132
+
133
+ switch (type) {
134
+ case "single":
135
+ uploadMiddleware = multerUpload.single(fieldName);
136
+ break;
137
+ case "array":
138
+ uploadMiddleware = multerUpload.array(fieldName, fields[0]?.maxCount);
139
+ break;
140
+ case "fields":
141
+ uploadMiddleware = multerUpload.fields(fields);
142
+ break;
143
+ default:
144
+ throw new Error("Unknown upload type");
145
+ }
146
+
147
+ return async (ctx, next) => {
148
+ try {
149
+ await uploadMiddleware(ctx, next);
150
+ } catch (err) {
151
+ handleMulterError(err, ctx);
152
+ }
153
+ };
154
+ };
155
+
156
+ return {
157
+ single: (fieldName = "file") => handleUpload("single", fieldName),//单文件上传
158
+ array: (fieldName = "files") => handleUpload("array", fieldName),//多文件上传 - 相同字段
159
+ fields: (fields) => handleUpload("fields", null, fields),//多文件上传 -不同字段
160
+ };
161
+ };
162
+
163
+ module.exports = uploadMiddleware;
164
+
165
+
@@ -0,0 +1,11 @@
1
+ const BaseDAO = require("./BaseDAO.js");
2
+
3
+ class Auth extends BaseDAO {
4
+ constructor() {
5
+ //表名users
6
+ super("users");
7
+ }
8
+
9
+ }
10
+
11
+ module.exports = new Auth();
@@ -0,0 +1,449 @@
1
+ const pool = require("../config/db");
2
+
3
+ class BaseDAO {
4
+ constructor(tableName) {
5
+ this.tableName = tableName;
6
+ this.pool = pool;
7
+ }
8
+
9
+ /**
10
+ * 类型转换工具方法
11
+ * @param {*} value :待转换的值
12
+ * @param {*} fieldType :字段类型
13
+ * @returns 转换后的值
14
+ */
15
+ convertValue(value, fieldType = "string") {
16
+ if (
17
+ value === undefined ||
18
+ value === null ||
19
+ value === "" ||
20
+ Array.isArray(value)
21
+ ) {
22
+ return value;
23
+ }
24
+
25
+ switch (fieldType) {
26
+ case "number":
27
+ case "int":
28
+ case "integer":
29
+ return parseInt(value, 10);
30
+
31
+ case "float":
32
+ case "decimal":
33
+ return parseFloat(value);
34
+
35
+ case "boolean":
36
+ case "bool":
37
+ if (value === "true" || value === "1" || value === 1) return true;
38
+ if (value === "false" || value === "0" || value === 0) return false;
39
+ return Boolean(value);
40
+
41
+ case "date":
42
+ return new Date(value);
43
+
44
+ case "string":
45
+ default:
46
+ return String(value);
47
+ }
48
+ }
49
+
50
+ /**
51
+ * 自动类型转换查询参数
52
+ * @param {*} params :待转换的参数
53
+ * @param {*} fieldTypes :字段类型
54
+ * @returns 转换后的参数
55
+ */
56
+ convertQueryParams(params, fieldTypes = {}) {
57
+ const converted = {};
58
+
59
+ for (const [key, value] of Object.entries(params)) {
60
+ const fieldType = fieldTypes[key] || "string";
61
+ converted[key] = this.convertValue(value, fieldType);
62
+ }
63
+
64
+ return converted;
65
+ }
66
+
67
+ /**
68
+ * 执行 SQL查询 ,execute方式
69
+ * @param {*} sql :SQL语句
70
+ * @param {*} params :SQL参数
71
+ * @returns 执行结果,查询语句返回数组,其他操作返回对象
72
+ */
73
+ async execute(sql, params) {
74
+ try {
75
+ const [result] = await this.pool.execute(sql, params);
76
+ return result;
77
+ } catch (error) {
78
+ console.error("Database execute error:", error);
79
+ throw error;
80
+ }
81
+ }
82
+
83
+ /**
84
+ * 执行 SQL查询 ,query方式
85
+ * @param {*} sql :SQL语句
86
+ * @param {*} params :SQL参数
87
+ * @returns 执行结果,查询语句返回数组,其他操作返回对象
88
+ */
89
+ async query(sql, params = []) {
90
+ try {
91
+ const [result] = await this.pool.query(sql, params);
92
+ return result;
93
+ } catch (error) {
94
+ console.error("Database query error:", error);
95
+ throw error;
96
+ }
97
+ }
98
+
99
+ /**
100
+ * 构建 WHERE 条件子句
101
+ * @param {*} conditions :查询条件
102
+ * @param {*} fieldTypes :字段类型
103
+ * @returns 构建后的WHERE条件子句和参数
104
+ */
105
+ buildWhereClause(conditions = {}, fieldTypes = {}) {
106
+ const whereConditions = [];
107
+ const params = [];
108
+
109
+ // 先进行类型转换
110
+ const convertedConditions = this.convertQueryParams(conditions, fieldTypes);
111
+
112
+ for (const [key, value] of Object.entries(convertedConditions)) {
113
+ // 跳过空值
114
+ if (value === undefined || value === null || value === "") {
115
+ continue;
116
+ }
117
+
118
+ // 处理模糊搜索条件(使用 LIKE)
119
+ if (key.startsWith("$like_")) {
120
+ const field = key.replace("$like_", "");
121
+ whereConditions.push(`${field} LIKE ?`);
122
+ params.push(`%${value}%`);
123
+ }
124
+ // 处理时间范围搜索(开始时间)
125
+ else if (key.startsWith("$gte_")) {
126
+ const field = key.replace("$gte_", "");
127
+ whereConditions.push(`${field} >= ?`);
128
+ params.push(value);
129
+ }
130
+ // 处理时间范围搜索(结束时间)
131
+ else if (key.startsWith("$lte_")) {
132
+ const field = key.replace("$lte_", "");
133
+ whereConditions.push(`${field} <= ?`);
134
+ params.push(value);
135
+ }
136
+ // 处理时间范围搜索(区间)
137
+ else if (key.startsWith("$between_")) {
138
+ const field = key.replace("$between_", "");
139
+ if (Array.isArray(value) && value.length === 2) {
140
+ whereConditions.push(`${field} BETWEEN ? AND ?`);
141
+ params.push(value[0], value[1]);
142
+ }
143
+ }
144
+ // 处理 IN 查询
145
+ else if (key.startsWith("$in_")) {
146
+ const field = key.replace("$in_", "");
147
+ if (Array.isArray(value) && value.length > 0) {
148
+ const placeholders = value.map(() => "?").join(", ");
149
+ whereConditions.push(`${field} IN (${placeholders})`);
150
+ params.push(...value);
151
+ }
152
+ }
153
+ // 普通等值查询
154
+ else {
155
+ whereConditions.push(`${key} = ?`);
156
+ params.push(value);
157
+ }
158
+ }
159
+
160
+ return {
161
+ whereClause:
162
+ whereConditions.length > 0
163
+ ? `WHERE ${whereConditions.join(" AND ")}`
164
+ : "",
165
+ params,
166
+ };
167
+ }
168
+
169
+ /**
170
+ * 查询所有记录
171
+ * @param {*} conditions :查询条件
172
+ * @param {*} options :查询选项
173
+ * @param {*} fieldTypes :字段类型
174
+ * @returns 查询结果
175
+ */
176
+ async findAll(conditions = {}, options = {}, fieldTypes = {}) {
177
+ let sql = `SELECT * FROM ${this.tableName}`;
178
+ // 构建 WHERE 条件
179
+ const { whereClause, params } = this.buildWhereClause(
180
+ conditions,
181
+ fieldTypes
182
+ );
183
+ sql += ` ${whereClause}`;
184
+
185
+ // 添加排序
186
+ if (options.orderBy) {
187
+ sql += ` ORDER BY ${options.orderBy}`;
188
+ if (options.orderDirection) {
189
+ sql += ` ${options.orderDirection}`;
190
+ }
191
+ }
192
+
193
+ // 添加分页
194
+ if (typeof options.limit === "number" && options.limit > 0) {
195
+ sql += " LIMIT ?";
196
+ params.push(BigInt(options.limit));
197
+
198
+ if (typeof options.offset === "number" && options.offset >= 0) {
199
+ sql += " OFFSET ?";
200
+ params.push(BigInt(options.offset));
201
+ }
202
+ }
203
+
204
+ return await this.execute(sql, params);
205
+ }
206
+
207
+ /**
208
+ * 根据 ID 查询单条记录
209
+ * @param {*} id :记录ID
210
+ * @returns 查询结果
211
+ */
212
+ async findById(id) {
213
+ const sql = `SELECT * FROM ${this.tableName} WHERE id = ?`;
214
+ const rows = await this.execute(sql, [id]);
215
+ return rows?.length > 0 ? rows[0] : null;
216
+ }
217
+
218
+ /**
219
+ * 查询单条记录
220
+ * @param {*} conditions :查询条件
221
+ * @returns 查询结果
222
+ */
223
+ async findOne(conditions = {}) {
224
+ const rows = await this.findAll(conditions, { limit: 1 });
225
+ return rows?.length > 0 ? rows[0] : null;
226
+ }
227
+
228
+ /**
229
+ * 检查某字段值是否存在数据库
230
+ * @param {*} key : 字段名
231
+ * @param {*} value : 字段值
232
+ * @returns : true/false
233
+ */
234
+ async isFieldValueExists(key, value) {
235
+ let conditions = {};
236
+ conditions[key] = value;
237
+ const row = await this.findOne(conditions);
238
+ return !!row;
239
+ }
240
+
241
+ /**
242
+ * 插入记录
243
+ */
244
+ async create(data) {
245
+ const keys = Object.keys(data);
246
+ const values = Object.values(data);
247
+ const placeholders = keys.map(() => "?").join(", ");
248
+
249
+ const sql = `INSERT INTO ${this.tableName} (${keys.join(
250
+ ", "
251
+ )}) VALUES (${placeholders})`;
252
+ const result = await this.execute(sql, values);
253
+
254
+ return {
255
+ id: result.insertId,
256
+ ...data,
257
+ };
258
+ }
259
+
260
+ /**
261
+ * 批量插入
262
+ */
263
+ async createMany(items) {
264
+ if (items.length === 0) return [];
265
+
266
+ const keys = Object.keys(items[0]);
267
+ const values = items.map((item) => Object.values(item));
268
+ const placeholders = items
269
+ .map(() => `(${keys.map(() => "?").join(", ")})`)
270
+ .join(", ");
271
+
272
+ const sql = `INSERT INTO ${this.tableName} (${keys.join(
273
+ ", "
274
+ )}) VALUES ${placeholders}`;
275
+ const result = await this.execute(sql, values.flat());
276
+
277
+ return result;
278
+ }
279
+
280
+ /**
281
+ * 更新记录
282
+ * @param {*} id :记录ID
283
+ * @param {*} data :更新数据
284
+ * @returns :true/false
285
+ */
286
+ async update(id, data) {
287
+ const keys = Object.keys(data);
288
+ const values = Object.values(data);
289
+
290
+ const setClause = keys.map((key) => `${key} = ?`).join(", ");
291
+ const sql = `UPDATE ${this.tableName} SET ${setClause} WHERE id = ?`;
292
+
293
+ const result = await this.execute(sql, [...values, id]);
294
+ return result.affectedRows > 0;
295
+ }
296
+
297
+ /**
298
+ * 条件更新
299
+ * @param {*} conditions :条件
300
+ * @param {*} data :更新数据
301
+ * @returns :true/false
302
+ */
303
+ async updateWhere(conditions, data) {
304
+ const dataKeys = Object.keys(data);
305
+ const conditionKeys = Object.keys(conditions);
306
+
307
+ const setClause = dataKeys.map((key) => `${key} = ?`).join(", ");
308
+
309
+ // 构建 WHERE 条件
310
+ const { whereClause, params: conditionParams } =
311
+ this.buildWhereClause(conditions);
312
+
313
+ const values = [...Object.values(data), ...conditionParams];
314
+ const sql = `UPDATE ${this.tableName} SET ${setClause} ${whereClause}`;
315
+
316
+ const result = await this.execute(sql, values);
317
+ return result.affectedRows > 0;
318
+ }
319
+
320
+ /**
321
+ * 删除记录
322
+ * @param {*} id :记录ID
323
+ * @returns :true/false
324
+ */
325
+ async delete(id) {
326
+ const sql = `DELETE FROM ${this.tableName} WHERE id = ?`;
327
+ const result = await this.execute(sql, [id]);
328
+ return result.affectedRows > 0;
329
+ }
330
+
331
+ /**
332
+ * 条件删除
333
+ * @param {*} conditions :查询条件
334
+ * @returns :删除的记录数
335
+ */
336
+ async deleteWhere(conditions) {
337
+ const { whereClause, params } = this.buildWhereClause(conditions);
338
+ const sql = `DELETE FROM ${this.tableName} ${whereClause}`;
339
+ const result = await this.execute(sql, params);
340
+ return result.affectedRows > 0;
341
+ }
342
+
343
+ /**
344
+ * 计数
345
+ * @param {*} conditions :查询条件
346
+ * @param {*} fieldTypes :字段类型
347
+ * @returns :记录数
348
+ */
349
+ async count(conditions = {}, fieldTypes = {}) {
350
+ let sql = `SELECT COUNT(*) as total FROM ${this.tableName}`;
351
+
352
+ // 构建 WHERE 条件
353
+ const { whereClause, params } = this.buildWhereClause(
354
+ conditions,
355
+ fieldTypes
356
+ );
357
+ sql += ` ${whereClause}`;
358
+
359
+ const rows = await this.execute(sql, params);
360
+ return rows[0].total;
361
+ }
362
+
363
+ /**
364
+ * 分页查询
365
+ * @param {*} page :页码
366
+ * @param {*} pageSize :每页数量
367
+ * @param {*} conditions :查询条件
368
+ * @param {*} options :查询选项
369
+ * @param {*} fieldTypes :字段类型
370
+ * @returns :分页结果
371
+ */
372
+ async paginate(
373
+ page = 1,
374
+ pageSize = 10,
375
+ conditions = {},
376
+ options = {},
377
+ fieldTypes = {}
378
+ ) {
379
+ page = parseInt(page);
380
+ pageSize = parseInt(pageSize);
381
+ if (isNaN(page) || page < 1) page = 1;
382
+ if (isNaN(pageSize) || pageSize < 1) size = 10;
383
+
384
+ const offset = (page - 1) * pageSize;
385
+ const data = await this.findAll(
386
+ conditions,
387
+ {
388
+ ...options,
389
+ limit: pageSize,
390
+ offset: offset,
391
+ },
392
+ fieldTypes
393
+ );
394
+
395
+ const total = await this.count(conditions, fieldTypes);
396
+ const totalPages = Math.ceil(total / pageSize);
397
+
398
+ return {
399
+ list: data,
400
+ pagination: {
401
+ page: parseInt(page),
402
+ pageSize: parseInt(pageSize),
403
+ total,
404
+ totalPages,
405
+ hasNext: page < totalPages,
406
+ hasPrev: page > 1,
407
+ },
408
+ };
409
+ }
410
+ /**
411
+ * 高级搜索方法(新增)
412
+ */
413
+ async advancedSearch(searchConfig = {}) {
414
+ const {
415
+ keyword = "",
416
+ keywordFields = [], // 要搜索的字段数组
417
+ conditions = {},
418
+ dateRangeField = "",
419
+ startDate = "",
420
+ endDate = "",
421
+ options = {},
422
+ } = searchConfig;
423
+
424
+ let finalConditions = { ...conditions };
425
+
426
+ // 处理关键词搜索
427
+ if (keyword && keywordFields.length > 0) {
428
+ // 如果有多个字段需要关键词搜索,使用 OR 条件
429
+ const keywordConditions = keywordFields.map((field) => ({
430
+ [`$like_${field}`]: keyword,
431
+ }));
432
+
433
+ // 这里简化处理,实际可以更复杂
434
+ finalConditions = {
435
+ ...finalConditions,
436
+ ...keywordConditions[0], //
437
+ };
438
+ }
439
+
440
+ // 处理时间范围
441
+ if (dateRangeField && startDate && endDate) {
442
+ finalConditions[`$between_${dateRangeField}`] = [startDate, endDate];
443
+ }
444
+
445
+ return await this.findAll(finalConditions, options);
446
+ }
447
+ }
448
+
449
+ module.exports = BaseDAO;