befly 3.9.38 → 3.9.40

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 +8 -8
  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,8 +3,9 @@
3
3
  * 测试多表联查相关功能(不支持表别名)
4
4
  */
5
5
 
6
- import { describe, test, expect } from 'bun:test';
7
- import { snakeCase } from 'es-toolkit/string';
6
+ import { describe, test, expect } from "bun:test";
7
+
8
+ import { snakeCase } from "es-toolkit/string";
8
9
 
9
10
  // ============================================
10
11
  // 辅助函数单元测试(模拟 DbHelper 私有方法)
@@ -21,17 +22,17 @@ function processTableName(table: string): string {
21
22
  * 处理联查字段(支持表名.字段名格式)
22
23
  */
23
24
  function processJoinField(field: string): string {
24
- if (field.includes('(') || field === '*' || field.startsWith('`')) {
25
+ if (field.includes("(") || field === "*" || field.startsWith("`")) {
25
26
  return field;
26
27
  }
27
28
 
28
- if (field.toUpperCase().includes(' AS ')) {
29
+ if (field.toUpperCase().includes(" AS ")) {
29
30
  const [fieldPart, aliasPart] = field.split(/\s+AS\s+/i);
30
31
  return `${processJoinField(fieldPart.trim())} AS ${aliasPart.trim()}`;
31
32
  }
32
33
 
33
- if (field.includes('.')) {
34
- const [tableName, fieldName] = field.split('.');
34
+ if (field.includes(".")) {
35
+ const [tableName, fieldName] = field.split(".");
35
36
  return `${snakeCase(tableName)}.${snakeCase(fieldName)}`;
36
37
  }
37
38
 
@@ -42,24 +43,24 @@ function processJoinField(field: string): string {
42
43
  * 处理联查 where 条件键名
43
44
  */
44
45
  function processJoinWhereKey(key: string): string {
45
- if (key === '$or' || key === '$and') {
46
+ if (key === "$or" || key === "$and") {
46
47
  return key;
47
48
  }
48
49
 
49
- if (key.includes('$')) {
50
- const lastDollarIndex = key.lastIndexOf('$');
50
+ if (key.includes("$")) {
51
+ const lastDollarIndex = key.lastIndexOf("$");
51
52
  const fieldPart = key.substring(0, lastDollarIndex);
52
53
  const operator = key.substring(lastDollarIndex);
53
54
 
54
- if (fieldPart.includes('.')) {
55
- const [tableName, fieldName] = fieldPart.split('.');
55
+ if (fieldPart.includes(".")) {
56
+ const [tableName, fieldName] = fieldPart.split(".");
56
57
  return `${snakeCase(tableName)}.${snakeCase(fieldName)}${operator}`;
57
58
  }
58
59
  return `${snakeCase(fieldPart)}${operator}`;
59
60
  }
60
61
 
61
- if (key.includes('.')) {
62
- const [tableName, fieldName] = key.split('.');
62
+ if (key.includes(".")) {
63
+ const [tableName, fieldName] = key.split(".");
63
64
  return `${snakeCase(tableName)}.${snakeCase(fieldName)}`;
64
65
  }
65
66
 
@@ -70,7 +71,7 @@ function processJoinWhereKey(key: string): string {
70
71
  * 递归处理联查 where 条件
71
72
  */
72
73
  function processJoinWhere(where: any): any {
73
- if (!where || typeof where !== 'object') return where;
74
+ if (!where || typeof where !== "object") return where;
74
75
 
75
76
  if (Array.isArray(where)) {
76
77
  return where.map((item) => processJoinWhere(item));
@@ -80,9 +81,9 @@ function processJoinWhere(where: any): any {
80
81
  for (const [key, value] of Object.entries(where)) {
81
82
  const newKey = processJoinWhereKey(key);
82
83
 
83
- if (key === '$or' || key === '$and') {
84
+ if (key === "$or" || key === "$and") {
84
85
  result[newKey] = (value as any[]).map((item) => processJoinWhere(item));
85
- } else if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
86
+ } else if (typeof value === "object" && value !== null && !Array.isArray(value)) {
86
87
  result[newKey] = processJoinWhere(value);
87
88
  } else {
88
89
  result[newKey] = value;
@@ -97,8 +98,8 @@ function processJoinWhere(where: any): any {
97
98
  function processJoinOrderBy(orderBy: string[]): string[] {
98
99
  if (!orderBy || !Array.isArray(orderBy)) return orderBy;
99
100
  return orderBy.map((item) => {
100
- if (typeof item !== 'string' || !item.includes('#')) return item;
101
- const [field, direction] = item.split('#');
101
+ if (typeof item !== "string" || !item.includes("#")) return item;
102
+ const [field, direction] = item.split("#");
102
103
  return `${processJoinField(field.trim())}#${direction.trim()}`;
103
104
  });
104
105
  }
@@ -107,201 +108,205 @@ function processJoinOrderBy(orderBy: string[]): string[] {
107
108
  // 测试用例
108
109
  // ============================================
109
110
 
110
- describe('DbHelper JOIN - processTableName', () => {
111
- test('普通表名转下划线', () => {
112
- expect(processTableName('userProfile')).toBe('user_profile');
113
- expect(processTableName('orderDetail')).toBe('order_detail');
114
- expect(processTableName('user')).toBe('user');
115
- expect(processTableName('order')).toBe('order');
111
+ describe("DbHelper JOIN - processTableName", () => {
112
+ test("普通表名转下划线", () => {
113
+ expect(processTableName("userProfile")).toBe("user_profile");
114
+ expect(processTableName("orderDetail")).toBe("order_detail");
115
+ expect(processTableName("user")).toBe("user");
116
+ expect(processTableName("order")).toBe("order");
116
117
  });
117
118
  });
118
119
 
119
- describe('DbHelper JOIN - processJoinField', () => {
120
- test('带表名的字段', () => {
121
- expect(processJoinField('order.userId')).toBe('order.user_id');
122
- expect(processJoinField('user.userName')).toBe('user.user_name');
123
- expect(processJoinField('order.createdAt')).toBe('order.created_at');
120
+ describe("DbHelper JOIN - processJoinField", () => {
121
+ test("带表名的字段", () => {
122
+ expect(processJoinField("order.userId")).toBe("order.user_id");
123
+ expect(processJoinField("user.userName")).toBe("user.user_name");
124
+ expect(processJoinField("order.createdAt")).toBe("order.created_at");
124
125
  });
125
126
 
126
- test('表名也转下划线', () => {
127
- expect(processJoinField('orderDetail.productId')).toBe('order_detail.product_id');
128
- expect(processJoinField('userProfile.avatarUrl')).toBe('user_profile.avatar_url');
127
+ test("表名也转下划线", () => {
128
+ expect(processJoinField("orderDetail.productId")).toBe("order_detail.product_id");
129
+ expect(processJoinField("userProfile.avatarUrl")).toBe("user_profile.avatar_url");
129
130
  });
130
131
 
131
- test('普通字段(无表名)', () => {
132
- expect(processJoinField('userName')).toBe('user_name');
133
- expect(processJoinField('createdAt')).toBe('created_at');
132
+ test("普通字段(无表名)", () => {
133
+ expect(processJoinField("userName")).toBe("user_name");
134
+ expect(processJoinField("createdAt")).toBe("created_at");
134
135
  });
135
136
 
136
- test('带 AS 别名的字段', () => {
137
- expect(processJoinField('order.totalAmount AS total')).toBe('order.total_amount AS total');
138
- expect(processJoinField('user.userName AS name')).toBe('user.user_name AS name');
139
- expect(processJoinField('product.name AS productName')).toBe('product.name AS productName');
137
+ test("带 AS 别名的字段", () => {
138
+ expect(processJoinField("order.totalAmount AS total")).toBe("order.total_amount AS total");
139
+ expect(processJoinField("user.userName AS name")).toBe("user.user_name AS name");
140
+ expect(processJoinField("product.name AS productName")).toBe("product.name AS productName");
140
141
  });
141
142
 
142
- test('函数字段保持原样', () => {
143
- expect(processJoinField('COUNT(*)')).toBe('COUNT(*)');
144
- expect(processJoinField('SUM(order.amount)')).toBe('SUM(order.amount)');
143
+ test("函数字段保持原样", () => {
144
+ expect(processJoinField("COUNT(*)")).toBe("COUNT(*)");
145
+ expect(processJoinField("SUM(order.amount)")).toBe("SUM(order.amount)");
145
146
  });
146
147
 
147
- test('星号保持原样', () => {
148
- expect(processJoinField('*')).toBe('*');
148
+ test("星号保持原样", () => {
149
+ expect(processJoinField("*")).toBe("*");
149
150
  });
150
151
 
151
- test('已转义字段保持原样', () => {
152
- expect(processJoinField('`order`')).toBe('`order`');
152
+ test("已转义字段保持原样", () => {
153
+ expect(processJoinField("`order`")).toBe("`order`");
153
154
  });
154
155
  });
155
156
 
156
- describe('DbHelper JOIN - processJoinWhereKey', () => {
157
- test('带表名的字段名', () => {
158
- expect(processJoinWhereKey('order.userId')).toBe('order.user_id');
159
- expect(processJoinWhereKey('user.userName')).toBe('user.user_name');
157
+ describe("DbHelper JOIN - processJoinWhereKey", () => {
158
+ test("带表名的字段名", () => {
159
+ expect(processJoinWhereKey("order.userId")).toBe("order.user_id");
160
+ expect(processJoinWhereKey("user.userName")).toBe("user.user_name");
160
161
  });
161
162
 
162
- test('带表名和操作符的字段名', () => {
163
- expect(processJoinWhereKey('order.createdAt$gt')).toBe('order.created_at$gt');
164
- expect(processJoinWhereKey('user.status$in')).toBe('user.status$in');
165
- expect(processJoinWhereKey('order.amount$gte')).toBe('order.amount$gte');
163
+ test("带表名和操作符的字段名", () => {
164
+ expect(processJoinWhereKey("order.createdAt$gt")).toBe("order.created_at$gt");
165
+ expect(processJoinWhereKey("user.status$in")).toBe("user.status$in");
166
+ expect(processJoinWhereKey("order.amount$gte")).toBe("order.amount$gte");
166
167
  });
167
168
 
168
- test('普通字段带操作符', () => {
169
- expect(processJoinWhereKey('createdAt$gt')).toBe('created_at$gt');
170
- expect(processJoinWhereKey('userId$ne')).toBe('user_id$ne');
169
+ test("普通字段带操作符", () => {
170
+ expect(processJoinWhereKey("createdAt$gt")).toBe("created_at$gt");
171
+ expect(processJoinWhereKey("userId$ne")).toBe("user_id$ne");
171
172
  });
172
173
 
173
- test('逻辑操作符保持原样', () => {
174
- expect(processJoinWhereKey('$or')).toBe('$or');
175
- expect(processJoinWhereKey('$and')).toBe('$and');
174
+ test("逻辑操作符保持原样", () => {
175
+ expect(processJoinWhereKey("$or")).toBe("$or");
176
+ expect(processJoinWhereKey("$and")).toBe("$and");
176
177
  });
177
178
  });
178
179
 
179
- describe('DbHelper JOIN - processJoinWhere', () => {
180
- test('简单条件', () => {
181
- const where = { 'order.userId': 1, 'order.state': 1 };
180
+ describe("DbHelper JOIN - processJoinWhere", () => {
181
+ test("简单条件", () => {
182
+ const where = { "order.userId": 1, "order.state": 1 };
182
183
  const result = processJoinWhere(where);
183
- expect(result).toEqual({ 'order.user_id': 1, 'order.state': 1 });
184
+ expect(result).toEqual({ "order.user_id": 1, "order.state": 1 });
184
185
  });
185
186
 
186
- test('带操作符的条件', () => {
187
- const where = { 'order.createdAt$gt': 1000, 'user.state$ne': 0 };
187
+ test("带操作符的条件", () => {
188
+ const where = { "order.createdAt$gt": 1000, "user.state$ne": 0 };
188
189
  const result = processJoinWhere(where);
189
- expect(result).toEqual({ 'order.created_at$gt': 1000, 'user.state$ne': 0 });
190
+ expect(result).toEqual({ "order.created_at$gt": 1000, "user.state$ne": 0 });
190
191
  });
191
192
 
192
- test('$or 条件', () => {
193
+ test("$or 条件", () => {
193
194
  const where = {
194
- $or: [{ 'user.userName$like': '%test%' }, { 'user.email$like': '%test%' }]
195
+ $or: [{ "user.userName$like": "%test%" }, { "user.email$like": "%test%" }]
195
196
  };
196
197
  const result = processJoinWhere(where);
197
198
  expect(result).toEqual({
198
- $or: [{ 'user.user_name$like': '%test%' }, { 'user.email$like': '%test%' }]
199
+ $or: [{ "user.user_name$like": "%test%" }, { "user.email$like": "%test%" }]
199
200
  });
200
201
  });
201
202
 
202
- test('复杂嵌套条件', () => {
203
+ test("复杂嵌套条件", () => {
203
204
  const where = {
204
- 'order.state': 1,
205
- 'user.state': 1,
206
- $or: [{ 'user.userName$like': '%test%' }, { 'product.name$like': '%test%' }],
207
- 'order.createdAt$gte': 1000
205
+ "order.state": 1,
206
+ "user.state": 1,
207
+ $or: [{ "user.userName$like": "%test%" }, { "product.name$like": "%test%" }],
208
+ "order.createdAt$gte": 1000
208
209
  };
209
210
  const result = processJoinWhere(where);
210
211
  expect(result).toEqual({
211
- 'order.state': 1,
212
- 'user.state': 1,
213
- $or: [{ 'user.user_name$like': '%test%' }, { 'product.name$like': '%test%' }],
214
- 'order.created_at$gte': 1000
212
+ "order.state": 1,
213
+ "user.state": 1,
214
+ $or: [{ "user.user_name$like": "%test%" }, { "product.name$like": "%test%" }],
215
+ "order.created_at$gte": 1000
215
216
  });
216
217
  });
217
218
  });
218
219
 
219
- describe('DbHelper JOIN - processJoinOrderBy', () => {
220
- test('带表名的排序', () => {
221
- const orderBy = ['order.createdAt#DESC', 'user.userName#ASC'];
220
+ describe("DbHelper JOIN - processJoinOrderBy", () => {
221
+ test("带表名的排序", () => {
222
+ const orderBy = ["order.createdAt#DESC", "user.userName#ASC"];
222
223
  const result = processJoinOrderBy(orderBy);
223
- expect(result).toEqual(['order.created_at#DESC', 'user.user_name#ASC']);
224
+ expect(result).toEqual(["order.created_at#DESC", "user.user_name#ASC"]);
224
225
  });
225
226
 
226
- test('普通排序', () => {
227
- const orderBy = ['createdAt#DESC'];
227
+ test("普通排序", () => {
228
+ const orderBy = ["createdAt#DESC"];
228
229
  const result = processJoinOrderBy(orderBy);
229
- expect(result).toEqual(['created_at#DESC']);
230
+ expect(result).toEqual(["created_at#DESC"]);
230
231
  });
231
232
 
232
- test('无排序方向的保持原样', () => {
233
- const orderBy = ['id'];
233
+ test("无排序方向的保持原样", () => {
234
+ const orderBy = ["id"];
234
235
  const result = processJoinOrderBy(orderBy);
235
- expect(result).toEqual(['id']);
236
+ expect(result).toEqual(["id"]);
236
237
  });
237
238
  });
238
239
 
239
- describe('DbHelper JOIN - JoinOption 类型验证', () => {
240
- test('LEFT JOIN(默认)', () => {
241
- const join = { table: 'user', on: 'order.user_id = user.id' };
240
+ describe("DbHelper JOIN - JoinOption 类型验证", () => {
241
+ test("LEFT JOIN(默认)", () => {
242
+ const join = { table: "user", on: "order.user_id = user.id" };
242
243
  expect(join.type).toBeUndefined();
243
- expect(join.table).toBe('user');
244
- expect(join.on).toBe('order.user_id = user.id');
244
+ expect(join.table).toBe("user");
245
+ expect(join.on).toBe("order.user_id = user.id");
245
246
  });
246
247
 
247
- test('INNER JOIN', () => {
248
- const join = { type: 'inner' as const, table: 'product', on: 'order.product_id = product.id' };
249
- expect(join.type).toBe('inner');
248
+ test("INNER JOIN", () => {
249
+ const join = { type: "inner" as const, table: "product", on: "order.product_id = product.id" };
250
+ expect(join.type).toBe("inner");
250
251
  });
251
252
 
252
- test('RIGHT JOIN', () => {
253
- const join = { type: 'right' as const, table: 'category', on: 'product.category_id = category.id' };
254
- expect(join.type).toBe('right');
253
+ test("RIGHT JOIN", () => {
254
+ const join = {
255
+ type: "right" as const,
256
+ table: "category",
257
+ on: "product.category_id = category.id"
258
+ };
259
+ expect(join.type).toBe("right");
255
260
  });
256
261
  });
257
262
 
258
- describe('DbHelper JOIN - 完整场景模拟', () => {
259
- test('订单列表联查参数处理', () => {
263
+ describe("DbHelper JOIN - 完整场景模拟", () => {
264
+ test("订单列表联查参数处理", () => {
260
265
  // 模拟输入
261
266
  const options = {
262
- table: 'order',
267
+ table: "order",
263
268
  joins: [
264
- { table: 'user', on: 'order.userId = user.id' },
265
- { table: 'product', on: 'order.productId = product.id' }
269
+ { table: "user", on: "order.userId = user.id" },
270
+ { table: "product", on: "order.productId = product.id" }
266
271
  ],
267
- fields: ['order.id', 'order.totalAmount', 'user.userName', 'product.name AS productName'],
272
+ fields: ["order.id", "order.totalAmount", "user.userName", "product.name AS productName"],
268
273
  where: {
269
- 'order.state': 1,
270
- 'user.state': 1,
271
- 'order.createdAt$gte': 1701388800000
274
+ "order.state": 1,
275
+ "user.state": 1,
276
+ "order.createdAt$gte": 1701388800000
272
277
  },
273
- orderBy: ['order.createdAt#DESC']
278
+ orderBy: ["order.createdAt#DESC"]
274
279
  };
275
280
 
276
281
  // 处理表名
277
282
  const processedTable = processTableName(options.table);
278
- expect(processedTable).toBe('order');
283
+ expect(processedTable).toBe("order");
279
284
 
280
285
  // 处理字段
281
286
  const processedFields = options.fields.map((f) => processJoinField(f));
282
- expect(processedFields).toEqual(['order.id', 'order.total_amount', 'user.user_name', 'product.name AS productName']);
287
+ expect(processedFields).toEqual(["order.id", "order.total_amount", "user.user_name", "product.name AS productName"]);
283
288
 
284
289
  // 处理 where
285
290
  const processedWhere = processJoinWhere(options.where);
286
291
  expect(processedWhere).toEqual({
287
- 'order.state': 1,
288
- 'user.state': 1,
289
- 'order.created_at$gte': 1701388800000
292
+ "order.state": 1,
293
+ "user.state": 1,
294
+ "order.created_at$gte": 1701388800000
290
295
  });
291
296
 
292
297
  // 处理 orderBy
293
298
  const processedOrderBy = processJoinOrderBy(options.orderBy);
294
- expect(processedOrderBy).toEqual(['order.created_at#DESC']);
299
+ expect(processedOrderBy).toEqual(["order.created_at#DESC"]);
295
300
 
296
301
  // 处理 joins
297
302
  const processedJoins = options.joins.map((j) => ({
298
- type: (j as any).type || 'left',
303
+ type: (j as any).type || "left",
299
304
  table: processTableName(j.table),
300
305
  on: j.on
301
306
  }));
302
307
  expect(processedJoins).toEqual([
303
- { type: 'left', table: 'user', on: 'order.userId = user.id' },
304
- { type: 'left', table: 'product', on: 'order.productId = product.id' }
308
+ { type: "left", table: "user", on: "order.userId = user.id" },
309
+ { type: "left", table: "product", on: "order.productId = product.id" }
305
310
  ]);
306
311
  });
307
312
  });
@@ -2,9 +2,11 @@
2
2
  * 验证 Redis 缓存的字段查询功能
3
3
  */
4
4
 
5
- import { RedisKeys, RedisTTL } from 'befly-shared/redisKeys';
5
+ import { CacheKeys } from "../lib/cacheKeys.js";
6
6
 
7
- console.log('\n========== Redis 缓存验证 ==========\n');
7
+ const TABLE_COLUMNS_CACHE_TTL_SECONDS = 3600;
8
+
9
+ console.log("\n========== Redis 缓存验证 ==========\n");
8
10
 
9
11
  // 模拟 Redis 缓存逻辑
10
12
  class MockRedis {
@@ -34,13 +36,13 @@ async function queryDatabase(table: string): Promise<string[]> {
34
36
  console.log(`🔍 查询数据库表结构: ${table}`);
35
37
  // 模拟数据库延迟
36
38
  await new Promise((resolve) => setTimeout(resolve, 3));
37
- return ['id', 'name', 'email', 'password', 'salt', 'created_at'];
39
+ return ["id", "name", "email", "password", "salt", "created_at"];
38
40
  }
39
41
 
40
42
  // 模拟 getTableColumns 方法
41
43
  async function getTableColumns(redis: MockRedis, table: string): Promise<string[]> {
42
44
  // 1. 先查 Redis 缓存
43
- const cacheKey = RedisKeys.tableColumns(table);
45
+ const cacheKey = CacheKeys.tableColumns(table);
44
46
  let columns = await redis.getObject<string[]>(cacheKey);
45
47
 
46
48
  if (columns && columns.length > 0) {
@@ -51,7 +53,7 @@ async function getTableColumns(redis: MockRedis, table: string): Promise<string[
51
53
  columns = await queryDatabase(table);
52
54
 
53
55
  // 3. 写入 Redis 缓存
54
- await redis.setObject(cacheKey, columns, RedisTTL.tableColumns);
56
+ await redis.setObject(cacheKey, columns, TABLE_COLUMNS_CACHE_TTL_SECONDS);
55
57
 
56
58
  return columns;
57
59
  }
@@ -59,63 +61,63 @@ async function getTableColumns(redis: MockRedis, table: string): Promise<string[
59
61
  async function test() {
60
62
  const redis = new MockRedis();
61
63
 
62
- console.log('【场景1】单进程多次查询\n');
64
+ console.log("【场景1】单进程多次查询\n");
63
65
 
64
66
  // 第1次查询(缓存未命中)
65
- console.log('--- 第1次查询 user 表 ---');
67
+ console.log("--- 第1次查询 user 表 ---");
66
68
  const start1 = Date.now();
67
- const columns1 = await getTableColumns(redis, 'user');
69
+ const columns1 = await getTableColumns(redis, "user");
68
70
  const time1 = Date.now() - start1;
69
- console.log(`结果: ${columns1.join(', ')}`);
71
+ console.log(`结果: ${columns1.join(", ")}`);
70
72
  console.log(`耗时: ${time1}ms\n`);
71
73
 
72
74
  // 第2次查询(缓存命中)
73
- console.log('--- 第2次查询 user 表 ---');
75
+ console.log("--- 第2次查询 user 表 ---");
74
76
  const start2 = Date.now();
75
- const columns2 = await getTableColumns(redis, 'user');
77
+ const columns2 = await getTableColumns(redis, "user");
76
78
  const time2 = Date.now() - start2;
77
- console.log(`结果: ${columns2.join(', ')}`);
79
+ console.log(`结果: ${columns2.join(", ")}`);
78
80
  console.log(`耗时: ${time2}ms\n`);
79
81
 
80
82
  // 第3次查询(缓存命中)
81
- console.log('--- 第3次查询 user 表 ---');
83
+ console.log("--- 第3次查询 user 表 ---");
82
84
  const start3 = Date.now();
83
- const columns3 = await getTableColumns(redis, 'user');
85
+ const columns3 = await getTableColumns(redis, "user");
84
86
  const time3 = Date.now() - start3;
85
- console.log(`结果: ${columns3.join(', ')}`);
87
+ console.log(`结果: ${columns3.join(", ")}`);
86
88
  console.log(`耗时: ${time3}ms\n`);
87
89
 
88
- console.log('【场景2】模拟 PM2 cluster(多进程共享 Redis)\n');
90
+ console.log("【场景2】模拟 PM2 cluster(多进程共享 Redis)\n");
89
91
 
90
92
  // 模拟 Worker 1 查询
91
- console.log('--- Worker 1 查询 article 表 ---');
93
+ console.log("--- Worker 1 查询 article 表 ---");
92
94
  const worker1Start = Date.now();
93
- const worker1Columns = await getTableColumns(redis, 'article');
95
+ const worker1Columns = await getTableColumns(redis, "article");
94
96
  const worker1Time = Date.now() - worker1Start;
95
- console.log(`结果: ${worker1Columns.join(', ')}`);
97
+ console.log(`结果: ${worker1Columns.join(", ")}`);
96
98
  console.log(`耗时: ${worker1Time}ms\n`);
97
99
 
98
100
  // 模拟 Worker 2 查询(共享 Redis 缓存)
99
- console.log('--- Worker 2 查询 article 表 ---');
101
+ console.log("--- Worker 2 查询 article 表 ---");
100
102
  const worker2Start = Date.now();
101
- const worker2Columns = await getTableColumns(redis, 'article');
103
+ const worker2Columns = await getTableColumns(redis, "article");
102
104
  const worker2Time = Date.now() - worker2Start;
103
- console.log(`结果: ${worker2Columns.join(', ')}`);
105
+ console.log(`结果: ${worker2Columns.join(", ")}`);
104
106
  console.log(`耗时: ${worker2Time}ms`);
105
107
  console.log(`✅ Worker 2 直接使用 Worker 1 的缓存,无需再查数据库\n`);
106
108
 
107
109
  // 模拟 Worker 3 查询(共享 Redis 缓存)
108
- console.log('--- Worker 3 查询 article 表 ---');
110
+ console.log("--- Worker 3 查询 article 表 ---");
109
111
  const worker3Start = Date.now();
110
- const worker3Columns = await getTableColumns(redis, 'article');
112
+ const worker3Columns = await getTableColumns(redis, "article");
111
113
  const worker3Time = Date.now() - worker3Start;
112
- console.log(`结果: ${worker3Columns.join(', ')}`);
114
+ console.log(`结果: ${worker3Columns.join(", ")}`);
113
115
  console.log(`耗时: ${worker3Time}ms`);
114
116
  console.log(`✅ Worker 3 直接使用 Worker 1 的缓存,无需再查数据库\n`);
115
117
 
116
- console.log('========== 验证完成 ==========\n');
118
+ console.log("========== 验证完成 ==========\n");
117
119
 
118
- console.log('📊 性能总结:');
120
+ console.log("📊 性能总结:");
119
121
  console.log(`- 首次查询(数据库): ${time1}ms`);
120
122
  console.log(`- 后续查询(Redis): ${time2}ms`);
121
123
  console.log(`- 性能提升: ${(time1 / time2).toFixed(1)}x`);