befly 3.9.38 → 3.9.39

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 (155) hide show
  1. package/README.md +37 -38
  2. package/befly.config.ts +62 -40
  3. package/checks/checkApi.ts +16 -16
  4. package/checks/checkApp.ts +19 -25
  5. package/checks/checkTable.ts +42 -42
  6. package/docs/README.md +42 -35
  7. package/docs/{api.md → api/api.md} +223 -231
  8. package/docs/cipher.md +71 -69
  9. package/docs/database.md +143 -141
  10. package/docs/{examples.md → guide/examples.md} +181 -181
  11. package/docs/guide/quickstart.md +331 -0
  12. package/docs/hooks/auth.md +38 -0
  13. package/docs/hooks/cors.md +28 -0
  14. package/docs/{hook.md → hooks/hook.md} +140 -57
  15. package/docs/hooks/parser.md +19 -0
  16. package/docs/hooks/rateLimit.md +47 -0
  17. package/docs/{redis.md → infra/redis.md} +84 -93
  18. package/docs/plugins/cipher.md +61 -0
  19. package/docs/plugins/database.md +128 -0
  20. package/docs/{plugin.md → plugins/plugin.md} +83 -81
  21. package/docs/quickstart.md +26 -26
  22. package/docs/{addon.md → reference/addon.md} +46 -46
  23. package/docs/{config.md → reference/config.md} +32 -80
  24. package/docs/{logger.md → reference/logger.md} +52 -52
  25. package/docs/{sync.md → reference/sync.md} +32 -35
  26. package/docs/{table.md → reference/table.md} +1 -1
  27. package/docs/{validator.md → reference/validator.md} +57 -57
  28. package/hooks/auth.ts +8 -4
  29. package/hooks/cors.ts +13 -13
  30. package/hooks/parser.ts +37 -17
  31. package/hooks/permission.ts +26 -14
  32. package/hooks/rateLimit.ts +276 -0
  33. package/hooks/validator.ts +7 -7
  34. package/lib/asyncContext.ts +43 -0
  35. package/lib/cacheHelper.ts +212 -77
  36. package/lib/cacheKeys.ts +38 -0
  37. package/lib/cipher.ts +30 -30
  38. package/lib/connect.ts +28 -28
  39. package/lib/dbHelper.ts +183 -102
  40. package/lib/jwt.ts +16 -16
  41. package/lib/logger.ts +610 -19
  42. package/lib/redisHelper.ts +185 -44
  43. package/lib/sqlBuilder.ts +90 -91
  44. package/lib/validator.ts +59 -39
  45. package/loader/loadApis.ts +48 -44
  46. package/loader/loadHooks.ts +40 -14
  47. package/loader/loadPlugins.ts +16 -17
  48. package/main.ts +57 -47
  49. package/package.json +47 -45
  50. package/paths.ts +15 -14
  51. package/plugins/cache.ts +5 -4
  52. package/plugins/cipher.ts +3 -3
  53. package/plugins/config.ts +2 -2
  54. package/plugins/db.ts +9 -9
  55. package/plugins/jwt.ts +3 -3
  56. package/plugins/logger.ts +8 -12
  57. package/plugins/redis.ts +8 -8
  58. package/plugins/tool.ts +6 -6
  59. package/router/api.ts +85 -56
  60. package/router/static.ts +12 -12
  61. package/sync/syncAll.ts +12 -12
  62. package/sync/syncApi.ts +55 -52
  63. package/sync/syncDb/apply.ts +20 -19
  64. package/sync/syncDb/constants.ts +25 -23
  65. package/sync/syncDb/ddl.ts +35 -36
  66. package/sync/syncDb/helpers.ts +6 -9
  67. package/sync/syncDb/schema.ts +10 -9
  68. package/sync/syncDb/sqlite.ts +7 -8
  69. package/sync/syncDb/table.ts +37 -35
  70. package/sync/syncDb/tableCreate.ts +21 -20
  71. package/sync/syncDb/types.ts +23 -20
  72. package/sync/syncDb/version.ts +10 -10
  73. package/sync/syncDb.ts +43 -36
  74. package/sync/syncDev.ts +74 -65
  75. package/sync/syncMenu.ts +190 -55
  76. package/tests/api-integration-array-number.test.ts +282 -0
  77. package/tests/befly-config-env.test.ts +78 -0
  78. package/tests/cacheHelper.test.ts +135 -104
  79. package/tests/cacheKeys.test.ts +41 -0
  80. package/tests/cipher.test.ts +90 -89
  81. package/tests/dbHelper-advanced.test.ts +140 -134
  82. package/tests/dbHelper-all-array-types.test.ts +316 -0
  83. package/tests/dbHelper-array-serialization.test.ts +258 -0
  84. package/tests/dbHelper-columns.test.ts +56 -55
  85. package/tests/dbHelper-execute.test.ts +45 -44
  86. package/tests/dbHelper-joins.test.ts +124 -119
  87. package/tests/fields-redis-cache.test.ts +29 -27
  88. package/tests/fields-validate.test.ts +38 -38
  89. package/tests/getClientIp.test.ts +54 -0
  90. package/tests/integration.test.ts +69 -67
  91. package/tests/jwt.test.ts +27 -26
  92. package/tests/logger.test.ts +267 -34
  93. package/tests/rateLimit-hook.test.ts +477 -0
  94. package/tests/redisHelper.test.ts +187 -188
  95. package/tests/redisKeys.test.ts +6 -73
  96. package/tests/scanConfig.test.ts +144 -0
  97. package/tests/sqlBuilder-advanced.test.ts +217 -215
  98. package/tests/sqlBuilder.test.ts +92 -91
  99. package/tests/sync-connection.test.ts +29 -29
  100. package/tests/syncDb-apply.test.ts +97 -96
  101. package/tests/syncDb-array-number.test.ts +160 -0
  102. package/tests/syncDb-constants.test.ts +48 -47
  103. package/tests/syncDb-ddl.test.ts +99 -98
  104. package/tests/syncDb-helpers.test.ts +29 -28
  105. package/tests/syncDb-schema.test.ts +61 -60
  106. package/tests/syncDb-types.test.ts +60 -59
  107. package/tests/syncMenu-paths.test.ts +68 -0
  108. package/tests/util.test.ts +42 -41
  109. package/tests/validator-array-number.test.ts +310 -0
  110. package/tests/validator-default.test.ts +373 -0
  111. package/tests/validator.test.ts +271 -266
  112. package/tsconfig.json +4 -5
  113. package/types/api.d.ts +7 -12
  114. package/types/befly.d.ts +60 -13
  115. package/types/cache.d.ts +8 -4
  116. package/types/common.d.ts +17 -9
  117. package/types/context.d.ts +2 -2
  118. package/types/crypto.d.ts +23 -0
  119. package/types/database.d.ts +19 -19
  120. package/types/hook.d.ts +2 -2
  121. package/types/jwt.d.ts +118 -0
  122. package/types/logger.d.ts +30 -0
  123. package/types/plugin.d.ts +4 -4
  124. package/types/redis.d.ts +7 -3
  125. package/types/roleApisCache.ts +23 -0
  126. package/types/sync.d.ts +10 -10
  127. package/types/table.d.ts +50 -9
  128. package/types/validate.d.ts +69 -0
  129. package/utils/addonHelper.ts +90 -0
  130. package/utils/arrayKeysToCamel.ts +18 -0
  131. package/utils/calcPerfTime.ts +13 -0
  132. package/utils/configTypes.ts +3 -0
  133. package/utils/cors.ts +19 -0
  134. package/utils/fieldClear.ts +75 -0
  135. package/utils/genShortId.ts +12 -0
  136. package/utils/getClientIp.ts +45 -0
  137. package/utils/keysToCamel.ts +22 -0
  138. package/utils/keysToSnake.ts +22 -0
  139. package/utils/modules.ts +98 -0
  140. package/utils/pickFields.ts +19 -0
  141. package/utils/process.ts +56 -0
  142. package/utils/regex.ts +225 -0
  143. package/utils/response.ts +115 -0
  144. package/utils/route.ts +23 -0
  145. package/utils/scanConfig.ts +142 -0
  146. package/utils/scanFiles.ts +48 -0
  147. package/.prettierignore +0 -2
  148. package/.prettierrc +0 -12
  149. package/docs/1-/345/237/272/346/234/254/344/273/213/347/273/215.md +0 -35
  150. package/docs/2-/345/210/235/346/255/245/344/275/223/351/252/214.md +0 -64
  151. package/docs/3-/347/254/254/344/270/200/344/270/252/346/216/245/345/217/243.md +0 -46
  152. package/docs/4-/346/223/215/344/275/234/346/225/260/346/215/256/345/272/223.md +0 -172
  153. package/hooks/requestLogger.ts +0 -84
  154. package/types/index.ts +0 -24
  155. package/util.ts +0 -283
@@ -3,117 +3,117 @@
3
3
  * 测试边界条件、错误处理、复杂场景
4
4
  */
5
5
 
6
- import { describe, test, expect } from 'bun:test';
6
+ import { describe, test, expect } from "bun:test";
7
7
 
8
- describe('DbHelper - 字段验证逻辑', () => {
9
- test('validateAndClassifyFields - 空数组应返回 all 类型', () => {
8
+ describe("DbHelper - 字段验证逻辑", () => {
9
+ test("validateAndClassifyFields - 空数组应返回 all 类型", () => {
10
10
  // 测试目的:验证空数组被正确识别为查询所有字段
11
11
  const mockHelper = {
12
12
  validateAndClassifyFields: (fields?: string[]) => {
13
13
  if (!fields || fields.length === 0) {
14
- return { type: 'all' as const, fields: [] };
14
+ return { type: "all" as const, fields: [] };
15
15
  }
16
- return { type: 'include' as const, fields: fields };
16
+ return { type: "include" as const, fields: fields };
17
17
  }
18
18
  };
19
19
 
20
20
  const result = mockHelper.validateAndClassifyFields([]);
21
- expect(result.type).toBe('all');
21
+ expect(result.type).toBe("all");
22
22
  expect(result.fields.length).toBe(0);
23
23
  });
24
24
 
25
- test('validateAndClassifyFields - undefined 应返回 all 类型', () => {
25
+ test("validateAndClassifyFields - undefined 应返回 all 类型", () => {
26
26
  const mockHelper = {
27
27
  validateAndClassifyFields: (fields?: string[]) => {
28
28
  if (!fields || fields.length === 0) {
29
- return { type: 'all' as const, fields: [] };
29
+ return { type: "all" as const, fields: [] };
30
30
  }
31
- return { type: 'include' as const, fields: fields };
31
+ return { type: "include" as const, fields: fields };
32
32
  }
33
33
  };
34
34
 
35
35
  const result = mockHelper.validateAndClassifyFields(undefined);
36
- expect(result.type).toBe('all');
36
+ expect(result.type).toBe("all");
37
37
  });
38
38
 
39
- test('validateAndClassifyFields - 星号应抛出错误', () => {
39
+ test("validateAndClassifyFields - 星号应抛出错误", () => {
40
40
  const mockHelper = {
41
41
  validateAndClassifyFields: (fields?: string[]) => {
42
42
  if (!fields || fields.length === 0) {
43
- return { type: 'all' as const, fields: [] };
43
+ return { type: "all" as const, fields: [] };
44
44
  }
45
- if (fields.some((f) => f === '*')) {
46
- throw new Error('fields 不支持 * 星号');
45
+ if (fields.some((f) => f === "*")) {
46
+ throw new Error("fields 不支持 * 星号");
47
47
  }
48
- return { type: 'include' as const, fields: fields };
48
+ return { type: "include" as const, fields: fields };
49
49
  }
50
50
  };
51
51
 
52
52
  expect(() => {
53
- mockHelper.validateAndClassifyFields(['id', '*', 'name']);
54
- }).toThrow('fields 不支持 * 星号');
53
+ mockHelper.validateAndClassifyFields(["id", "*", "name"]);
54
+ }).toThrow("fields 不支持 * 星号");
55
55
  });
56
56
 
57
- test('validateAndClassifyFields - 空字符串应抛出错误', () => {
57
+ test("validateAndClassifyFields - 空字符串应抛出错误", () => {
58
58
  const mockHelper = {
59
59
  validateAndClassifyFields: (fields?: string[]) => {
60
- if (fields.some((f) => !f || typeof f !== 'string' || f.trim() === '')) {
61
- throw new Error('fields 不能包含空字符串或无效值');
60
+ if (fields.some((f) => !f || typeof f !== "string" || f.trim() === "")) {
61
+ throw new Error("fields 不能包含空字符串或无效值");
62
62
  }
63
- return { type: 'include' as const, fields: fields };
63
+ return { type: "include" as const, fields: fields };
64
64
  }
65
65
  };
66
66
 
67
67
  expect(() => {
68
- mockHelper.validateAndClassifyFields(['id', '', 'name']);
69
- }).toThrow('fields 不能包含空字符串或无效值');
68
+ mockHelper.validateAndClassifyFields(["id", "", "name"]);
69
+ }).toThrow("fields 不能包含空字符串或无效值");
70
70
  });
71
71
 
72
- test('validateAndClassifyFields - 混用包含和排除字段应抛出错误', () => {
72
+ test("validateAndClassifyFields - 混用包含和排除字段应抛出错误", () => {
73
73
  const mockHelper = {
74
74
  validateAndClassifyFields: (fields?: string[]) => {
75
- const includeFields = fields!.filter((f) => !f.startsWith('!'));
76
- const excludeFields = fields!.filter((f) => f.startsWith('!'));
75
+ const includeFields = fields!.filter((f) => !f.startsWith("!"));
76
+ const excludeFields = fields!.filter((f) => f.startsWith("!"));
77
77
 
78
78
  if (includeFields.length > 0 && excludeFields.length > 0) {
79
- throw new Error('fields 不能同时包含普通字段和排除字段');
79
+ throw new Error("fields 不能同时包含普通字段和排除字段");
80
80
  }
81
81
 
82
82
  if (excludeFields.length > 0) {
83
- return { type: 'exclude' as const, fields: excludeFields.map((f) => f.substring(1)) };
83
+ return { type: "exclude" as const, fields: excludeFields.map((f) => f.substring(1)) };
84
84
  }
85
85
 
86
- return { type: 'include' as const, fields: includeFields };
86
+ return { type: "include" as const, fields: includeFields };
87
87
  }
88
88
  };
89
89
 
90
90
  expect(() => {
91
- mockHelper.validateAndClassifyFields(['id', '!password', 'name']);
92
- }).toThrow('fields 不能同时包含普通字段和排除字段');
91
+ mockHelper.validateAndClassifyFields(["id", "!password", "name"]);
92
+ }).toThrow("fields 不能同时包含普通字段和排除字段");
93
93
  });
94
94
 
95
- test('validateAndClassifyFields - 排除字段应正确去除感叹号', () => {
95
+ test("validateAndClassifyFields - 排除字段应正确去除感叹号", () => {
96
96
  const mockHelper = {
97
97
  validateAndClassifyFields: (fields?: string[]) => {
98
- const excludeFields = fields!.filter((f) => f.startsWith('!'));
99
- return { type: 'exclude' as const, fields: excludeFields.map((f) => f.substring(1)) };
98
+ const excludeFields = fields!.filter((f) => f.startsWith("!"));
99
+ return { type: "exclude" as const, fields: excludeFields.map((f) => f.substring(1)) };
100
100
  }
101
101
  };
102
102
 
103
- const result = mockHelper.validateAndClassifyFields(['!password', '!token', '!salt']);
104
- expect(result.type).toBe('exclude');
105
- expect(result.fields).toEqual(['password', 'token', 'salt']);
103
+ const result = mockHelper.validateAndClassifyFields(["!password", "!token", "!salt"]);
104
+ expect(result.type).toBe("exclude");
105
+ expect(result.fields).toEqual(["password", "token", "salt"]);
106
106
  });
107
107
  });
108
108
 
109
- describe('DbHelper - BIGINT 字段转换逻辑', () => {
110
- test('convertBigIntFields - 白名单字段应转换', () => {
109
+ describe("DbHelper - BIGINT 字段转换逻辑", () => {
110
+ test("convertBigIntFields - 白名单字段应转换", () => {
111
111
  const mockConvert = (arr: any[]) => {
112
112
  return arr.map((item) => {
113
113
  const converted = { ...item };
114
- const whiteList = ['id', 'pid', 'sort'];
114
+ const whiteList = ["id", "pid", "sort"];
115
115
  for (const key of whiteList) {
116
- if (key in converted && typeof converted[key] === 'string') {
116
+ if (key in converted && typeof converted[key] === "string") {
117
117
  converted[key] = Number(converted[key]);
118
118
  }
119
119
  }
@@ -121,21 +121,21 @@ describe('DbHelper - BIGINT 字段转换逻辑', () => {
121
121
  });
122
122
  };
123
123
 
124
- const data = [{ id: '123', pid: '456', sort: '10', name: 'test' }];
124
+ const data = [{ id: "123", pid: "456", sort: "10", name: "test" }];
125
125
  const result = mockConvert(data);
126
126
 
127
127
  expect(result[0].id).toBe(123);
128
128
  expect(result[0].pid).toBe(456);
129
129
  expect(result[0].sort).toBe(10);
130
- expect(typeof result[0].id).toBe('number');
130
+ expect(typeof result[0].id).toBe("number");
131
131
  });
132
132
 
133
- test('convertBigIntFields - Id 后缀字段应转换', () => {
133
+ test("convertBigIntFields - Id 后缀字段应转换", () => {
134
134
  const mockConvert = (arr: any[]) => {
135
135
  return arr.map((item) => {
136
136
  const converted = { ...item };
137
137
  for (const [key, value] of Object.entries(converted)) {
138
- if ((key.endsWith('Id') || key.endsWith('_id')) && typeof value === 'string') {
138
+ if ((key.endsWith("Id") || key.endsWith("_id")) && typeof value === "string") {
139
139
  converted[key] = Number(value);
140
140
  }
141
141
  }
@@ -143,7 +143,7 @@ describe('DbHelper - BIGINT 字段转换逻辑', () => {
143
143
  });
144
144
  };
145
145
 
146
- const data = [{ userId: '100', roleId: '200', category_id: '300', name: 'test' }];
146
+ const data = [{ userId: "100", roleId: "200", category_id: "300", name: "test" }];
147
147
  const result = mockConvert(data);
148
148
 
149
149
  expect(result[0].userId).toBe(100);
@@ -151,12 +151,12 @@ describe('DbHelper - BIGINT 字段转换逻辑', () => {
151
151
  expect(result[0].category_id).toBe(300);
152
152
  });
153
153
 
154
- test('convertBigIntFields - At 后缀字段应转换(时间戳)', () => {
154
+ test("convertBigIntFields - At 后缀字段应转换(时间戳)", () => {
155
155
  const mockConvert = (arr: any[]) => {
156
156
  return arr.map((item) => {
157
157
  const converted = { ...item };
158
158
  for (const [key, value] of Object.entries(converted)) {
159
- if ((key.endsWith('At') || key.endsWith('_at')) && typeof value === 'string') {
159
+ if ((key.endsWith("At") || key.endsWith("_at")) && typeof value === "string") {
160
160
  converted[key] = Number(value);
161
161
  }
162
162
  }
@@ -164,7 +164,7 @@ describe('DbHelper - BIGINT 字段转换逻辑', () => {
164
164
  });
165
165
  };
166
166
 
167
- const data = [{ createdAt: '1609459200000', updatedAt: '1609459300000', deleted_at: '0' }];
167
+ const data = [{ createdAt: "1609459200000", updatedAt: "1609459300000", deleted_at: "0" }];
168
168
  const result = mockConvert(data);
169
169
 
170
170
  expect(result[0].createdAt).toBe(1609459200000);
@@ -172,12 +172,12 @@ describe('DbHelper - BIGINT 字段转换逻辑', () => {
172
172
  expect(result[0].deleted_at).toBe(0);
173
173
  });
174
174
 
175
- test('convertBigIntFields - 非数字字符串不应转换', () => {
175
+ test("convertBigIntFields - 非数字字符串不应转换", () => {
176
176
  const mockConvert = (arr: any[]) => {
177
177
  return arr.map((item) => {
178
178
  const converted = { ...item };
179
179
  for (const [key, value] of Object.entries(converted)) {
180
- if (key.endsWith('Id') && typeof value === 'string') {
180
+ if (key.endsWith("Id") && typeof value === "string") {
181
181
  const num = Number(value);
182
182
  if (!isNaN(num)) {
183
183
  converted[key] = num;
@@ -188,20 +188,20 @@ describe('DbHelper - BIGINT 字段转换逻辑', () => {
188
188
  });
189
189
  };
190
190
 
191
- const data = [{ userId: 'invalid', roleId: '123' }];
191
+ const data = [{ userId: "invalid", roleId: "123" }];
192
192
  const result = mockConvert(data);
193
193
 
194
- expect(result[0].userId).toBe('invalid'); // 保持原值
194
+ expect(result[0].userId).toBe("invalid"); // 保持原值
195
195
  expect(result[0].roleId).toBe(123);
196
196
  });
197
197
 
198
- test('convertBigIntFields - null 和 undefined 应跳过', () => {
198
+ test("convertBigIntFields - null 和 undefined 应跳过", () => {
199
199
  const mockConvert = (arr: any[]) => {
200
200
  return arr.map((item) => {
201
201
  const converted = { ...item };
202
202
  for (const [key, value] of Object.entries(converted)) {
203
203
  if (value === null || value === undefined) continue;
204
- if (key.endsWith('Id') && typeof value === 'string') {
204
+ if (key.endsWith("Id") && typeof value === "string") {
205
205
  converted[key] = Number(value);
206
206
  }
207
207
  }
@@ -209,7 +209,7 @@ describe('DbHelper - BIGINT 字段转换逻辑', () => {
209
209
  });
210
210
  };
211
211
 
212
- const data = [{ userId: null, roleId: undefined, categoryId: '123' }];
212
+ const data = [{ userId: null, roleId: undefined, categoryId: "123" }];
213
213
  const result = mockConvert(data);
214
214
 
215
215
  expect(result[0].userId).toBeNull();
@@ -217,7 +217,7 @@ describe('DbHelper - BIGINT 字段转换逻辑', () => {
217
217
  expect(result[0].categoryId).toBe(123);
218
218
  });
219
219
 
220
- test('convertBigIntFields - 空数组应返回空数组', () => {
220
+ test("convertBigIntFields - 空数组应返回空数组", () => {
221
221
  const mockConvert = (arr: any[]) => {
222
222
  if (!arr || !Array.isArray(arr)) return arr;
223
223
  return arr;
@@ -227,7 +227,7 @@ describe('DbHelper - BIGINT 字段转换逻辑', () => {
227
227
  expect(result).toEqual([]);
228
228
  });
229
229
 
230
- test('convertBigIntFields - 非数组应返回原值', () => {
230
+ test("convertBigIntFields - 非数组应返回原值", () => {
231
231
  const mockConvert = (arr: any) => {
232
232
  if (!arr || !Array.isArray(arr)) return arr;
233
233
  return arr;
@@ -238,8 +238,8 @@ describe('DbHelper - BIGINT 字段转换逻辑', () => {
238
238
  });
239
239
  });
240
240
 
241
- describe('DbHelper - WHERE 条件键名转换', () => {
242
- test('whereKeysToSnake - 简单字段名应转换为下划线', () => {
241
+ describe("DbHelper - WHERE 条件键名转换", () => {
242
+ test("whereKeysToSnake - 简单字段名应转换为下划线", () => {
243
243
  const snakeCase = (str: string) => str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
244
244
 
245
245
  const mockConvert = (where: any): any => {
@@ -250,21 +250,21 @@ describe('DbHelper - WHERE 条件键名转换', () => {
250
250
  return result;
251
251
  };
252
252
 
253
- const where = { userId: 123, userName: 'john' };
253
+ const where = { userId: 123, userName: "john" };
254
254
  const result = mockConvert(where);
255
255
 
256
256
  expect(result.user_id).toBe(123);
257
- expect(result.user_name).toBe('john');
257
+ expect(result.user_name).toBe("john");
258
258
  });
259
259
 
260
- test('whereKeysToSnake - 带操作符的字段名应正确处理', () => {
260
+ test("whereKeysToSnake - 带操作符的字段名应正确处理", () => {
261
261
  const snakeCase = (str: string) => str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
262
262
 
263
263
  const mockConvert = (where: any): any => {
264
264
  const result: any = {};
265
265
  for (const [key, value] of Object.entries(where)) {
266
- if (key.includes('$')) {
267
- const lastDollarIndex = key.lastIndexOf('$');
266
+ if (key.includes("$")) {
267
+ const lastDollarIndex = key.lastIndexOf("$");
268
268
  const fieldName = key.substring(0, lastDollarIndex);
269
269
  const operator = key.substring(lastDollarIndex);
270
270
  result[snakeCase(fieldName) + operator] = value;
@@ -275,25 +275,25 @@ describe('DbHelper - WHERE 条件键名转换', () => {
275
275
  return result;
276
276
  };
277
277
 
278
- const where = { userId$gt: 100, userName$like: '%john%' };
278
+ const where = { userId$gt: 100, userName$like: "%john%" };
279
279
  const result = mockConvert(where);
280
280
 
281
281
  expect(result.user_id$gt).toBe(100);
282
- expect(result.user_name$like).toBe('%john%');
282
+ expect(result.user_name$like).toBe("%john%");
283
283
  });
284
284
 
285
- test('whereKeysToSnake - $or 和 $and 应递归处理', () => {
285
+ test("whereKeysToSnake - $or 和 $and 应递归处理", () => {
286
286
  const snakeCase = (str: string) => str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
287
287
 
288
288
  const mockConvert = (where: any): any => {
289
- if (!where || typeof where !== 'object') return where;
289
+ if (!where || typeof where !== "object") return where;
290
290
  if (Array.isArray(where)) {
291
291
  return where.map((item) => mockConvert(item));
292
292
  }
293
293
 
294
294
  const result: any = {};
295
295
  for (const [key, value] of Object.entries(where)) {
296
- if (key === '$or' || key === '$and') {
296
+ if (key === "$or" || key === "$and") {
297
297
  result[key] = (value as any[]).map((item) => mockConvert(item));
298
298
  } else {
299
299
  result[snakeCase(key)] = value;
@@ -303,24 +303,24 @@ describe('DbHelper - WHERE 条件键名转换', () => {
303
303
  };
304
304
 
305
305
  const where = {
306
- $or: [{ userId: 1 }, { userName: 'john' }]
306
+ $or: [{ userId: 1 }, { userName: "john" }]
307
307
  };
308
308
  const result = mockConvert(where);
309
309
 
310
310
  expect(result.$or[0].user_id).toBe(1);
311
- expect(result.$or[1].user_name).toBe('john');
311
+ expect(result.$or[1].user_name).toBe("john");
312
312
  });
313
313
 
314
- test('whereKeysToSnake - 嵌套对象应递归转换', () => {
314
+ test("whereKeysToSnake - 嵌套对象应递归转换", () => {
315
315
  const snakeCase = (str: string) => str.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
316
316
 
317
317
  const mockConvert = (where: any): any => {
318
- if (!where || typeof where !== 'object') return where;
318
+ if (!where || typeof where !== "object") return where;
319
319
 
320
320
  const result: any = {};
321
321
  for (const [key, value] of Object.entries(where)) {
322
322
  const snakeKey = snakeCase(key);
323
- if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
323
+ if (typeof value === "object" && value !== null && !Array.isArray(value)) {
324
324
  result[snakeKey] = mockConvert(value);
325
325
  } else {
326
326
  result[snakeKey] = value;
@@ -331,19 +331,19 @@ describe('DbHelper - WHERE 条件键名转换', () => {
331
331
 
332
332
  const where = {
333
333
  userProfile: {
334
- firstName: 'John',
335
- lastName: 'Doe'
334
+ firstName: "John",
335
+ lastName: "Doe"
336
336
  }
337
337
  };
338
338
  const result = mockConvert(where);
339
339
 
340
- expect(result.user_profile.first_name).toBe('John');
341
- expect(result.user_profile.last_name).toBe('Doe');
340
+ expect(result.user_profile.first_name).toBe("John");
341
+ expect(result.user_profile.last_name).toBe("Doe");
342
342
  });
343
343
  });
344
344
 
345
- describe('DbHelper - 默认状态过滤', () => {
346
- test('addDefaultStateFilter - 应添加 state > 0 条件', () => {
345
+ describe("DbHelper - 默认状态过滤", () => {
346
+ test("addDefaultStateFilter - 应添加 state > 0 条件", () => {
347
347
  const mockAddFilter = (where: any) => {
348
348
  return {
349
349
  ...where,
@@ -358,13 +358,13 @@ describe('DbHelper - 默认状态过滤', () => {
358
358
  expect(result.state$gt).toBe(0);
359
359
  });
360
360
 
361
- test('addDefaultStateFilter - 已有 state 条件不应覆盖', () => {
361
+ test("addDefaultStateFilter - 已有 state 条件不应覆盖", () => {
362
362
  // **潜在问题**:如果用户传入 state: 0(查询已删除数据),
363
363
  // addDefaultStateFilter 可能会添加 state$gt: 0,导致冲突
364
364
 
365
365
  const mockAddFilter = (where: any) => {
366
366
  // 正确做法:检查是否已有 state 相关条件
367
- if ('state' in where || 'state$gt' in where || 'state$gte' in where) {
367
+ if ("state" in where || "state$gt" in where || "state$gte" in where) {
368
368
  return where; // 不添加默认过滤
369
369
  }
370
370
  return {
@@ -384,49 +384,49 @@ describe('DbHelper - 默认状态过滤', () => {
384
384
  });
385
385
  });
386
386
 
387
- describe('DbHelper - 分页参数验证', () => {
388
- test('getList - page 小于 1 应抛出错误', () => {
389
- const mockValidate = (page: number, limit: number) => {
387
+ describe("DbHelper - 分页参数验证", () => {
388
+ test("getList - page 小于 1 应抛出错误", () => {
389
+ const mockValidate = (page: number, _limit: number) => {
390
390
  if (page < 1 || page > 10000) {
391
391
  throw new Error(`页码必须在 1 到 10000 之间 (page: ${page})`);
392
392
  }
393
393
  };
394
394
 
395
- expect(() => mockValidate(0, 10)).toThrow('页码必须在 1 到 10000 之间');
396
- expect(() => mockValidate(-5, 10)).toThrow('页码必须在 1 到 10000 之间');
395
+ expect(() => mockValidate(0, 10)).toThrow("页码必须在 1 到 10000 之间");
396
+ expect(() => mockValidate(-5, 10)).toThrow("页码必须在 1 到 10000 之间");
397
397
  });
398
398
 
399
- test('getList - page 大于 10000 应抛出错误', () => {
399
+ test("getList - page 大于 10000 应抛出错误", () => {
400
400
  const mockValidate = (page: number) => {
401
401
  if (page > 10000) {
402
402
  throw new Error(`页码必须在 1 到 10000 之间 (page: ${page})`);
403
403
  }
404
404
  };
405
405
 
406
- expect(() => mockValidate(10001)).toThrow('页码必须在 1 到 10000 之间');
406
+ expect(() => mockValidate(10001)).toThrow("页码必须在 1 到 10000 之间");
407
407
  });
408
408
 
409
- test('getList - limit 小于 1 应抛出错误', () => {
409
+ test("getList - limit 小于 1 应抛出错误", () => {
410
410
  const mockValidate = (limit: number) => {
411
411
  if (limit < 1 || limit > 1000) {
412
412
  throw new Error(`每页数量必须在 1 到 1000 之间 (limit: ${limit})`);
413
413
  }
414
414
  };
415
415
 
416
- expect(() => mockValidate(0)).toThrow('每页数量必须在 1 到 1000 之间');
416
+ expect(() => mockValidate(0)).toThrow("每页数量必须在 1 到 1000 之间");
417
417
  });
418
418
 
419
- test('getList - limit 大于 1000 应抛出错误', () => {
419
+ test("getList - limit 大于 1000 应抛出错误", () => {
420
420
  const mockValidate = (limit: number) => {
421
421
  if (limit > 1000) {
422
422
  throw new Error(`每页数量必须在 1 到 1000 之间 (limit: ${limit})`);
423
423
  }
424
424
  };
425
425
 
426
- expect(() => mockValidate(1001)).toThrow('每页数量必须在 1 到 1000 之间');
426
+ expect(() => mockValidate(1001)).toThrow("每页数量必须在 1 到 1000 之间");
427
427
  });
428
428
 
429
- test('getList - 边界值测试', () => {
429
+ test("getList - 边界值测试", () => {
430
430
  const mockValidate = (page: number, limit: number) => {
431
431
  if (page < 1 || page > 10000) {
432
432
  throw new Error(`页码无效`);
@@ -443,8 +443,8 @@ describe('DbHelper - 分页参数验证', () => {
443
443
  });
444
444
  });
445
445
 
446
- describe('DbHelper - 总数为 0 的优化', () => {
447
- test('getList - 总数为 0 应直接返回空结果', async () => {
446
+ describe("DbHelper - 总数为 0 的优化", () => {
447
+ test("getList - 总数为 0 应直接返回空结果", async () => {
448
448
  const mockGetList = async (total: number, page: number, limit: number) => {
449
449
  if (total === 0) {
450
450
  return {
@@ -471,7 +471,7 @@ describe('DbHelper - 总数为 0 的优化', () => {
471
471
  expect(result.pages).toBe(0);
472
472
  });
473
473
 
474
- test('getList - pages 计算应正确', () => {
474
+ test("getList - pages 计算应正确", () => {
475
475
  const calcPages = (total: number, limit: number) => Math.ceil(total / limit);
476
476
 
477
477
  expect(calcPages(100, 10)).toBe(10); // 整除
@@ -481,8 +481,8 @@ describe('DbHelper - 总数为 0 的优化', () => {
481
481
  });
482
482
  });
483
483
 
484
- describe('DbHelper - 字段清理逻辑', () => {
485
- test('cleanFields - 应排除 null 和 undefined', () => {
484
+ describe("DbHelper - 字段清理逻辑", () => {
485
+ test("cleanFields - 应排除 null 和 undefined", () => {
486
486
  const mockClean = (data: any) => {
487
487
  const result: any = {};
488
488
  for (const [key, value] of Object.entries(data)) {
@@ -493,16 +493,16 @@ describe('DbHelper - 字段清理逻辑', () => {
493
493
  return result;
494
494
  };
495
495
 
496
- const data = { name: 'test', age: null, email: undefined, score: 0 };
496
+ const data = { name: "test", age: null, email: undefined, score: 0 };
497
497
  const result = mockClean(data);
498
498
 
499
- expect(result.name).toBe('test');
499
+ expect(result.name).toBe("test");
500
500
  expect(result.age).toBeUndefined();
501
501
  expect(result.email).toBeUndefined();
502
502
  expect(result.score).toBe(0); // 0 应保留
503
503
  });
504
504
 
505
- test('cleanFields - 空对象应返回空对象', () => {
505
+ test("cleanFields - 空对象应返回空对象", () => {
506
506
  const mockClean = (data: any) => {
507
507
  if (!data || Object.keys(data).length === 0) return {};
508
508
  return data;
@@ -514,12 +514,12 @@ describe('DbHelper - 字段清理逻辑', () => {
514
514
  });
515
515
  });
516
516
 
517
- describe('DbHelper - SQL 执行错误处理', () => {
518
- test('executeWithConn - SQL 错误应包含完整信息', () => {
517
+ describe("DbHelper - SQL 执行错误处理", () => {
518
+ test("executeWithConn - SQL 错误应包含完整信息", () => {
519
519
  const mockExecute = async (sql: string, params?: any[]) => {
520
520
  try {
521
521
  // 模拟 SQL 错误
522
- throw new Error('Table not found');
522
+ throw new Error("Table not found");
523
523
  } catch (error: any) {
524
524
  const errorMsg = `SQL 执行失败 - ${error.message} - SQL: ${sql} - 参数: ${JSON.stringify(params)}`;
525
525
  throw new Error(errorMsg);
@@ -527,28 +527,28 @@ describe('DbHelper - SQL 执行错误处理', () => {
527
527
  };
528
528
 
529
529
  expect(async () => {
530
- await mockExecute('SELECT * FROM non_existent_table', []);
530
+ await mockExecute("SELECT * FROM non_existent_table", []);
531
531
  }).toThrow();
532
532
  });
533
533
 
534
- test('executeWithConn - 超长 SQL 应截断', () => {
534
+ test("executeWithConn - 超长 SQL 应截断", () => {
535
535
  const mockTruncate = (sql: string, maxLength: number = 500) => {
536
536
  if (sql.length > maxLength) {
537
- return sql.substring(0, maxLength) + '...';
537
+ return sql.substring(0, maxLength) + "...";
538
538
  }
539
539
  return sql;
540
540
  };
541
541
 
542
- const longSql = 'SELECT * FROM users WHERE ' + 'name = ? AND '.repeat(100) + 'id = ?';
542
+ const longSql = "SELECT * FROM users WHERE " + "name = ? AND ".repeat(100) + "id = ?";
543
543
  const truncated = mockTruncate(longSql, 100);
544
544
 
545
545
  expect(truncated.length).toBeLessThanOrEqual(103); // 100 + "..."
546
- expect(truncated.endsWith('...')).toBe(true);
546
+ expect(truncated.endsWith("...")).toBe(true);
547
547
  });
548
548
  });
549
549
 
550
- describe('DbHelper - 代码逻辑问题分析', () => {
551
- test('问题1:addDefaultStateFilter 可能覆盖用户的 state 条件', () => {
550
+ describe("DbHelper - 代码逻辑问题分析", () => {
551
+ test("问题1:addDefaultStateFilter 可能覆盖用户的 state 条件", () => {
552
552
  // **问题描述**:
553
553
  // 当前实现:addDefaultStateFilter 直接添加 state$gt: 0
554
554
  // 问题:如果用户想查询 state=0(已删除)或 state=2(已禁用)的数据,
@@ -565,7 +565,7 @@ describe('DbHelper - 代码逻辑问题分析', () => {
565
565
 
566
566
  const betterImpl = (where: any) => {
567
567
  // 检查是否已有 state 条件
568
- const hasStateCondition = Object.keys(where).some((key) => key === 'state' || key.startsWith('state$'));
568
+ const hasStateCondition = Object.keys(where).some((key) => key === "state" || key.startsWith("state$"));
569
569
 
570
570
  if (hasStateCondition) {
571
571
  return where; // 不添加默认过滤
@@ -588,7 +588,7 @@ describe('DbHelper - 代码逻辑问题分析', () => {
588
588
  expect(result2.state$gt).toBeUndefined(); // 正确
589
589
  });
590
590
 
591
- test('问题2:getTableColumns 缓存键没有区分数据库', () => {
591
+ test("问题2:getTableColumns 缓存键没有区分数据库", () => {
592
592
  // **问题描述**:
593
593
  // 缓存键格式:table:columns:${table}
594
594
  // 问题:如果连接多个数据库,不同数据库的同名表会共享缓存
@@ -601,15 +601,15 @@ describe('DbHelper - 代码逻辑问题分析', () => {
601
601
  const betterCacheKey = (dbName: string, table: string) => `table:columns:${dbName}:${table}`;
602
602
 
603
603
  // 问题示例
604
- expect(currentCacheKey('user')).toBe('table:columns:user');
604
+ expect(currentCacheKey("user")).toBe("table:columns:user");
605
605
  // db1.user 和 db2.user 会冲突
606
606
 
607
607
  // 改进后
608
- expect(betterCacheKey('db1', 'user')).toBe('table:columns:db1:user');
609
- expect(betterCacheKey('db2', 'user')).toBe('table:columns:db2:user');
608
+ expect(betterCacheKey("db1", "user")).toBe("table:columns:db1:user");
609
+ expect(betterCacheKey("db2", "user")).toBe("table:columns:db2:user");
610
610
  });
611
611
 
612
- test('问题3:convertBigIntFields 白名单硬编码', () => {
612
+ test("问题3:convertBigIntFields 白名单硬编码", () => {
613
613
  // **问题描述**:
614
614
  // 白名单字段硬编码为 ['id', 'pid', 'sort']
615
615
  // 问题:如果有其他 BIGINT 字段不符合命名规则,需要修改源码
@@ -619,11 +619,11 @@ describe('DbHelper - 代码逻辑问题分析', () => {
619
619
  // 2. 或者从表结构元数据中自动获取 BIGINT 字段
620
620
 
621
621
  const currentImpl = (arr: any[]) => {
622
- const whiteList = ['id', 'pid', 'sort']; // 硬编码
622
+ const whiteList = ["id", "pid", "sort"]; // 硬编码
623
623
  return arr.map((item) => {
624
624
  const converted = { ...item };
625
625
  for (const key of whiteList) {
626
- if (key in converted && typeof converted[key] === 'string') {
626
+ if (key in converted && typeof converted[key] === "string") {
627
627
  converted[key] = Number(converted[key]);
628
628
  }
629
629
  }
@@ -632,13 +632,13 @@ describe('DbHelper - 代码逻辑问题分析', () => {
632
632
  };
633
633
 
634
634
  const betterImpl = (arr: any[], customWhiteList?: string[]) => {
635
- const defaultWhiteList = ['id', 'pid', 'sort'];
635
+ const defaultWhiteList = ["id", "pid", "sort"];
636
636
  const whiteList = customWhiteList || defaultWhiteList;
637
637
 
638
638
  return arr.map((item) => {
639
639
  const converted = { ...item };
640
640
  for (const key of whiteList) {
641
- if (key in converted && typeof converted[key] === 'string') {
641
+ if (key in converted && typeof converted[key] === "string") {
642
642
  converted[key] = Number(converted[key]);
643
643
  }
644
644
  }
@@ -647,12 +647,18 @@ describe('DbHelper - 代码逻辑问题分析', () => {
647
647
  };
648
648
 
649
649
  // 改进后支持自定义
650
- const data = [{ id: '123', customId: '456' }];
651
- const result = betterImpl(data, ['id', 'customId']);
650
+ const data = [{ id: "123", customId: "456" }];
651
+
652
+ // 当前实现:只能转换硬编码白名单字段
653
+ const currentResult = currentImpl(data);
654
+ expect(currentResult[0].id).toBe(123);
655
+ expect(currentResult[0].customId).toBe("456");
656
+
657
+ const result = betterImpl(data, ["id", "customId"]);
652
658
  expect(result[0].customId).toBe(456);
653
659
  });
654
660
 
655
- test('问题4:executeWithConn 没有超时保护', async () => {
661
+ test("问题4:executeWithConn 没有超时保护", async () => {
656
662
  // **问题描述**:
657
663
  // 当前实现没有查询超时限制
658
664
  // 问题:慢查询可能导致长时间阻塞
@@ -662,7 +668,7 @@ describe('DbHelper - 代码逻辑问题分析', () => {
662
668
 
663
669
  const mockExecuteWithTimeout = async (sql: string, params: any[], timeout: number = 30000) => {
664
670
  const timeoutPromise = new Promise((_, reject) => {
665
- setTimeout(() => reject(new Error('查询超时')), timeout);
671
+ setTimeout(() => reject(new Error("查询超时")), timeout);
666
672
  });
667
673
 
668
674
  const queryPromise = new Promise((resolve) => {
@@ -673,19 +679,19 @@ describe('DbHelper - 代码逻辑问题分析', () => {
673
679
  };
674
680
 
675
681
  // 正常查询
676
- const result = await mockExecuteWithTimeout('SELECT * FROM users', [], 1000);
682
+ const result = await mockExecuteWithTimeout("SELECT * FROM users", [], 1000);
677
683
  expect(result).toBeDefined();
678
684
 
679
685
  // 超时查询
680
686
  try {
681
- await mockExecuteWithTimeout('SELECT * FROM users', [], 10); // 10ms 超时
687
+ await mockExecuteWithTimeout("SELECT * FROM users", [], 10); // 10ms 超时
682
688
  expect(true).toBe(false); // 不应执行到这里
683
689
  } catch (error: any) {
684
- expect(error.message).toContain('查询超时');
690
+ expect(error.message).toContain("查询超时");
685
691
  }
686
692
  });
687
693
 
688
- test('问题5:getAll 的 MAX_LIMIT 保护不够完善', async () => {
694
+ test("问题5:getAll 的 MAX_LIMIT 保护不够完善", async () => {
689
695
  // **问题描述**:
690
696
  // getAll 设置了 MAX_LIMIT = 10000
691
697
  // 问题:但没有检测实际查询的数据量,可能超过限制
@@ -707,11 +713,11 @@ describe('DbHelper - 代码逻辑问题分析', () => {
707
713
  };
708
714
 
709
715
  try {
710
- await betterGetAll('users', 10000);
716
+ await betterGetAll("users", 10000);
711
717
  expect(true).toBe(false);
712
718
  } catch (error: any) {
713
- expect(error.message).toContain('数据量过大');
714
- expect(error.message).toContain('请使用 getList 分页查询');
719
+ expect(error.message).toContain("数据量过大");
720
+ expect(error.message).toContain("请使用 getList 分页查询");
715
721
  }
716
722
  });
717
723
  });