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
@@ -29,15 +29,15 @@ Befly 框架使用 Redis 作为缓存层,提供高性能的数据缓存、会
29
29
  ```typescript
30
30
  // 在 API handler 中
31
31
  export default {
32
- name: '示例接口',
32
+ name: "示例接口",
33
33
  handler: async (befly, ctx) => {
34
34
  // 设置缓存
35
- await befly.redis.setObject('user:1', { name: '张三', age: 25 });
35
+ await befly.redis.setObject("user:1", { name: "张三", age: 25 });
36
36
 
37
37
  // 获取缓存
38
- const user = await befly.redis.getObject('user:1');
38
+ const user = await befly.redis.getObject("user:1");
39
39
 
40
- return befly.tool.Yes('成功', user);
40
+ return befly.tool.Yes("成功", user);
41
41
  }
42
42
  };
43
43
  ```
@@ -52,16 +52,16 @@ export default {
52
52
 
53
53
  ```typescript
54
54
  // 基本设置
55
- await befly.redis.setString('key', 'value');
55
+ await befly.redis.setString("key", "value");
56
56
 
57
57
  // 带过期时间(秒)
58
- await befly.redis.setString('key', 'value', 3600); // 1小时后过期
58
+ await befly.redis.setString("key", "value", 3600); // 1小时后过期
59
59
  ```
60
60
 
61
61
  #### getString - 获取字符串
62
62
 
63
63
  ```typescript
64
- const value = await befly.redis.getString('key');
64
+ const value = await befly.redis.getString("key");
65
65
  // 返回: 'value' 或 null(不存在时)
66
66
  ```
67
67
 
@@ -73,14 +73,14 @@ const value = await befly.redis.getString('key');
73
73
 
74
74
  ```typescript
75
75
  // 基本设置
76
- await befly.redis.setObject('user:1', {
76
+ await befly.redis.setObject("user:1", {
77
77
  id: 1,
78
- name: '张三',
79
- roles: ['admin', 'user']
78
+ name: "张三",
79
+ roles: ["admin", "user"]
80
80
  });
81
81
 
82
82
  // 带过期时间(秒)
83
- await befly.redis.setObject('session:abc123', { userId: 1 }, 7200); // 2小时
83
+ await befly.redis.setObject("session:abc123", { userId: 1 }, 7200); // 2小时
84
84
  ```
85
85
 
86
86
  #### getObject - 获取对象
@@ -88,14 +88,14 @@ await befly.redis.setObject('session:abc123', { userId: 1 }, 7200); // 2小时
88
88
  自动反序列化 JSON。
89
89
 
90
90
  ```typescript
91
- const user = await befly.redis.getObject<UserInfo>('user:1');
91
+ const user = await befly.redis.getObject<UserInfo>("user:1");
92
92
  // 返回: { id: 1, name: '张三', roles: ['admin', 'user'] } 或 null
93
93
  ```
94
94
 
95
95
  #### delObject - 删除对象
96
96
 
97
97
  ```typescript
98
- await befly.redis.delObject('user:1');
98
+ await befly.redis.delObject("user:1");
99
99
  ```
100
100
 
101
101
  ### 键操作
@@ -103,27 +103,27 @@ await befly.redis.delObject('user:1');
103
103
  #### exists - 检查键是否存在
104
104
 
105
105
  ```typescript
106
- const exists = await befly.redis.exists('user:1');
106
+ const exists = await befly.redis.exists("user:1");
107
107
  // 返回: true 或 false
108
108
  ```
109
109
 
110
110
  #### del - 删除键
111
111
 
112
112
  ```typescript
113
- const count = await befly.redis.del('user:1');
113
+ const count = await befly.redis.del("user:1");
114
114
  // 返回: 删除的键数量(0 或 1)
115
115
  ```
116
116
 
117
117
  #### expire - 设置过期时间
118
118
 
119
119
  ```typescript
120
- await befly.redis.expire('user:1', 3600); // 1小时后过期
120
+ await befly.redis.expire("user:1", 3600); // 1小时后过期
121
121
  ```
122
122
 
123
123
  #### ttl - 获取剩余过期时间
124
124
 
125
125
  ```typescript
126
- const seconds = await befly.redis.ttl('user:1');
126
+ const seconds = await befly.redis.ttl("user:1");
127
127
  // 返回: 剩余秒数,-1 表示永不过期,-2 表示键不存在
128
128
  ```
129
129
 
@@ -135,30 +135,30 @@ const seconds = await befly.redis.ttl('user:1');
135
135
 
136
136
  ```typescript
137
137
  // 添加单个成员
138
- await befly.redis.sadd('tags:article:1', ['技术']);
138
+ await befly.redis.sadd("tags:article:1", ["技术"]);
139
139
 
140
140
  // 添加多个成员
141
- await befly.redis.sadd('user:1:roles', ['admin', 'editor', 'viewer']);
141
+ await befly.redis.sadd("user:1:roles", ["admin", "editor", "viewer"]);
142
142
  ```
143
143
 
144
144
  #### sismember - 检查成员是否存在
145
145
 
146
146
  ```typescript
147
- const isMember = await befly.redis.sismember('user:1:roles', 'admin');
147
+ const isMember = await befly.redis.sismember("user:1:roles", "admin");
148
148
  // 返回: true 或 false
149
149
  ```
150
150
 
151
151
  #### smembers - 获取所有成员
152
152
 
153
153
  ```typescript
154
- const roles = await befly.redis.smembers('user:1:roles');
154
+ const roles = await befly.redis.smembers("user:1:roles");
155
155
  // 返回: ['admin', 'editor', 'viewer']
156
156
  ```
157
157
 
158
158
  #### scard - 获取成员数量
159
159
 
160
160
  ```typescript
161
- const count = await befly.redis.scard('user:1:roles');
161
+ const count = await befly.redis.scard("user:1:roles");
162
162
  // 返回: 3
163
163
  ```
164
164
 
@@ -172,9 +172,9 @@ const count = await befly.redis.scard('user:1:roles');
172
172
 
173
173
  ```typescript
174
174
  const count = await befly.redis.setBatch([
175
- { key: 'user:1', value: { name: '张三' }, ttl: 3600 },
176
- { key: 'user:2', value: { name: '李四' }, ttl: 3600 },
177
- { key: 'user:3', value: { name: '王五' } } // 无 TTL,永不过期
175
+ { key: "user:1", value: { name: "张三" }, ttl: 3600 },
176
+ { key: "user:2", value: { name: "李四" }, ttl: 3600 },
177
+ { key: "user:3", value: { name: "王五" } } // 无 TTL,永不过期
178
178
  ]);
179
179
  // 返回: 成功设置的数量
180
180
  ```
@@ -182,28 +182,28 @@ const count = await befly.redis.setBatch([
182
182
  ### getBatch - 批量获取对象
183
183
 
184
184
  ```typescript
185
- const users = await befly.redis.getBatch<UserInfo>(['user:1', 'user:2', 'user:3']);
185
+ const users = await befly.redis.getBatch<UserInfo>(["user:1", "user:2", "user:3"]);
186
186
  // 返回: [{ name: '张三' }, { name: '李四' }, null](不存在的返回 null)
187
187
  ```
188
188
 
189
189
  ### delBatch - 批量删除键
190
190
 
191
191
  ```typescript
192
- const count = await befly.redis.delBatch(['user:1', 'user:2', 'user:3']);
192
+ const count = await befly.redis.delBatch(["user:1", "user:2", "user:3"]);
193
193
  // 返回: 成功删除的数量
194
194
  ```
195
195
 
196
196
  ### existsBatch - 批量检查存在
197
197
 
198
198
  ```typescript
199
- const results = await befly.redis.existsBatch(['user:1', 'user:2', 'user:3']);
199
+ const results = await befly.redis.existsBatch(["user:1", "user:2", "user:3"]);
200
200
  // 返回: [true, true, false]
201
201
  ```
202
202
 
203
203
  ### ttlBatch - 批量获取过期时间
204
204
 
205
205
  ```typescript
206
- const ttls = await befly.redis.ttlBatch(['user:1', 'user:2', 'user:3']);
206
+ const ttls = await befly.redis.ttlBatch(["user:1", "user:2", "user:3"]);
207
207
  // 返回: [3600, 7200, -1]
208
208
  ```
209
209
 
@@ -211,8 +211,8 @@ const ttls = await befly.redis.ttlBatch(['user:1', 'user:2', 'user:3']);
211
211
 
212
212
  ```typescript
213
213
  const count = await befly.redis.expireBatch([
214
- { key: 'user:1', seconds: 3600 },
215
- { key: 'user:2', seconds: 7200 }
214
+ { key: "user:1", seconds: 3600 },
215
+ { key: "user:2", seconds: 7200 }
216
216
  ]);
217
217
  // 返回: 成功设置的数量
218
218
  ```
@@ -221,8 +221,8 @@ const count = await befly.redis.expireBatch([
221
221
 
222
222
  ```typescript
223
223
  const count = await befly.redis.saddBatch([
224
- { key: 'role:admin:apis', members: ['GET/api/user', 'POST/api/user'] },
225
- { key: 'role:editor:apis', members: ['GET/api/article', 'POST/api/article'] }
224
+ { key: "role:admin:apis", members: ["GET/api/user", "POST/api/user"] },
225
+ { key: "role:editor:apis", members: ["GET/api/article", "POST/api/article"] }
226
226
  ]);
227
227
  // 返回: 成功添加的总成员数量
228
228
  ```
@@ -231,8 +231,8 @@ const count = await befly.redis.saddBatch([
231
231
 
232
232
  ```typescript
233
233
  const results = await befly.redis.sismemberBatch([
234
- { key: 'role:admin:apis', member: 'GET/api/user' },
235
- { key: 'role:admin:apis', member: 'DELETE/api/user' }
234
+ { key: "role:admin:apis", member: "GET/api/user" },
235
+ { key: "role:admin:apis", member: "DELETE/api/user" }
236
236
  ]);
237
237
  // 返回: [true, false]
238
238
  ```
@@ -257,10 +257,10 @@ const id = await befly.redis.genTimeID();
257
257
  ```typescript
258
258
  // 在 DbHelper.insData 中自动调用
259
259
  const id = await befly.db.insData({
260
- table: 'article',
260
+ table: "article",
261
261
  data: {
262
- title: '文章标题',
263
- content: '文章内容'
262
+ title: "文章标题",
263
+ content: "文章内容"
264
264
  }
265
265
  });
266
266
  // id 由 genTimeID 自动生成
@@ -277,24 +277,19 @@ const id = await befly.db.insData({
277
277
 
278
278
  ## 缓存键管理
279
279
 
280
- ### RedisKeys - 统一键名管理
280
+ ### CacheKeys - 统一键名管理
281
281
 
282
282
  避免硬编码,统一管理所有缓存键。
283
283
 
284
284
  ```typescript
285
- import { RedisKeys, RedisTTL } from 'befly-shared/redisKeys';
285
+ import { CacheKeys } from "befly/lib/cacheKeys";
286
286
 
287
287
  // 获取键名
288
- const key = RedisKeys.apisAll(); // 'befly:apis:all'
289
- const key = RedisKeys.menusAll(); // 'befly:menus:all'
290
- const key = RedisKeys.roleInfo('admin'); // 'befly:role:info:admin'
291
- const key = RedisKeys.roleApis('admin'); // 'befly:role:apis:admin'
292
- const key = RedisKeys.tableColumns('user'); // 'befly:table:columns:user'
293
-
294
- // 获取 TTL
295
- const ttl = RedisTTL.tableColumns; // 3600(1小时)
296
- const ttl = RedisTTL.roleApis; // 86400(24小时)
297
- const ttl = RedisTTL.apisAll; // null(永不过期)
288
+ const key = CacheKeys.apisAll(); // 'befly:apis:all'
289
+ const key = CacheKeys.menusAll(); // 'befly:menus:all'
290
+ const key = CacheKeys.roleInfo("admin"); // 'befly:role:info:admin'
291
+ const key = CacheKeys.roleApis("admin"); // 'befly:role:apis:admin'
292
+ const key = CacheKeys.tableColumns("user"); // 'befly:table:columns:user'
298
293
  ```
299
294
 
300
295
  ### 键名前缀
@@ -318,31 +313,27 @@ Redis 插件支持配置全局前缀,避免键名冲突:
318
313
  ### 场景1:表结构缓存
319
314
 
320
315
  DbHelper 自动缓存表字段信息,避免重复查询数据库。
316
+ // 计数 + 过期:常用于限流/风控
317
+ // 更推荐:直接使用 Befly Core 内置的 rateLimit hook(通过 configs 配置即可)
321
318
 
322
- ```typescript
323
- // 首次查询 - 缓存未命中,查询数据库
324
- const columns = await befly.db.getTableColumns('user');
325
- // ❌ Redis 缓存未命中
326
- // 🔍 查询数据库表结构
327
- // 📝 写入 Redis 缓存 (TTL: 3600s)
319
+ const limit = 100; // 60 秒内最多 100 次
320
+ const windowSeconds = 60;
328
321
 
329
- // 后续查询 - 直接从缓存获取
330
- const columns = await befly.db.getTableColumns('user');
331
- // ✅ Redis 缓存命中
332
- ```
322
+ const key = `ratelimit:${ctx.ip}:${ctx.route}`;
323
+ const count = await befly.redis.incrWithExpire(key, windowSeconds);
333
324
 
334
- **PM2 Cluster 模式:** 多个 Worker 进程共享同一份 Redis 缓存,只有第一个进程需要查询数据库。
325
+ if (count > limit) {
326
+ return befly.tool.No("请求过于频繁");
327
+ }
335
328
 
336
329
  ### 场景2:接口权限缓存
337
330
 
338
331
  使用 Set 集合存储角色的接口权限,实现 O(1) 时间复杂度的权限检查。
339
332
 
340
333
  ```typescript
341
- // 缓存角色权限(启动时自动执行)
342
- await befly.redis.sadd('befly:role:apis:admin', ['GET/api/user/list', 'POST/api/user/add', 'DELETE/api/user/del']);
343
-
344
- // 权限检查(请求时)
345
- const hasPermission = await befly.redis.sismember('befly:role:apis:admin', 'POST/api/user/add');
334
+ // 极简方案:每个角色一个 Set
335
+ const roleApisKey = CacheKeys.roleApis("admin");
336
+ const hasPermission = await befly.redis.sismember(roleApisKey, "POST/api/user/add");
346
337
  // 返回: true
347
338
  ```
348
339
 
@@ -365,7 +356,7 @@ await befly.redis.setObject(
365
356
  // 验证会话
366
357
  const session = await befly.redis.getObject(`session:${sessionId}`);
367
358
  if (!session) {
368
- return befly.tool.No('会话已过期');
359
+ return befly.tool.No("会话已过期");
369
360
  }
370
361
 
371
362
  // 登出时删除会话
@@ -376,16 +367,16 @@ await befly.redis.delObject(`session:${sessionId}`);
376
367
 
377
368
  ```typescript
378
369
  // 用户登出时,将 token 加入黑名单
379
- const token = ctx.req.headers.get('Authorization')?.replace('Bearer ', '');
370
+ const token = ctx.req.headers.get("Authorization")?.replace("Bearer ", "");
380
371
  if (token) {
381
372
  const key = `token:blacklist:${token}`;
382
- await befly.redis.setString(key, '1', 7 * 24 * 60 * 60); // 7天
373
+ await befly.redis.setString(key, "1", 7 * 24 * 60 * 60); // 7天
383
374
  }
384
375
 
385
376
  // 验证时检查黑名单
386
377
  const isBlacklisted = await befly.redis.exists(`token:blacklist:${token}`);
387
378
  if (isBlacklisted) {
388
- return befly.tool.No('Token 已失效');
379
+ return befly.tool.No("Token 已失效");
389
380
  }
390
381
  ```
391
382
 
@@ -399,11 +390,11 @@ const count = current ? parseInt(current) : 0;
399
390
 
400
391
  if (count >= 100) {
401
392
  // 每分钟最多 100 次
402
- return befly.tool.No('请求过于频繁');
393
+ return befly.tool.No("请求过于频繁");
403
394
  }
404
395
 
405
396
  if (count === 0) {
406
- await befly.redis.setString(key, '1', 60); // 60秒窗口
397
+ await befly.redis.setString(key, "1", 60); // 60秒窗口
407
398
  } else {
408
399
  await befly.redis.setString(key, String(count + 1), await befly.redis.ttl(key));
409
400
  }
@@ -414,10 +405,10 @@ if (count === 0) {
414
405
  ```typescript
415
406
  // 获取锁
416
407
  const lockKey = `lock:order:${orderId}`;
417
- const acquired = await befly.redis.setString(lockKey, '1', 30); // 30秒自动释放
408
+ const acquired = await befly.redis.setString(lockKey, "1", 30); // 30秒自动释放
418
409
 
419
410
  if (!acquired) {
420
- return befly.tool.No('操作正在进行中,请稍后');
411
+ return befly.tool.No("操作正在进行中,请稍后");
421
412
  }
422
413
 
423
414
  try {
@@ -433,15 +424,15 @@ try {
433
424
 
434
425
  ```typescript
435
426
  // 获取热门文章(带缓存)
436
- const cacheKey = 'articles:hot:10';
427
+ const cacheKey = "articles:hot:10";
437
428
  let articles = await befly.redis.getObject(cacheKey);
438
429
 
439
430
  if (!articles) {
440
431
  // 缓存未命中,查询数据库
441
432
  const result = await befly.db.getAll({
442
- table: 'article',
443
- fields: ['id', 'title', 'viewCount'],
444
- orderBy: ['viewCount#DESC']
433
+ table: "article",
434
+ fields: ["id", "title", "viewCount"],
435
+ orderBy: ["viewCount#DESC"]
445
436
  });
446
437
 
447
438
  articles = result.lists; // 获取数据列表(最多 10000 条)
@@ -450,7 +441,7 @@ if (!articles) {
450
441
  await befly.redis.setObject(cacheKey, articles, 300);
451
442
  }
452
443
 
453
- return befly.tool.Yes('成功', articles);
444
+ return befly.tool.Yes("成功", articles);
454
445
  ```
455
446
 
456
447
  ---
@@ -481,7 +472,7 @@ const apis = await befly.cache.getApis();
481
472
  const menus = await befly.cache.getMenus();
482
473
 
483
474
  // 获取角色权限
484
- const permissions = await befly.cache.getRolePermissions('admin');
475
+ const permissions = await befly.cache.getRolePermissions("admin");
485
476
  // 返回: ['GET/api/user/list', 'POST/api/user/add', ...]
486
477
  ```
487
478
 
@@ -489,7 +480,7 @@ const permissions = await befly.cache.getRolePermissions('admin');
489
480
 
490
481
  ```typescript
491
482
  // 检查角色是否有指定接口权限
492
- const hasPermission = await befly.cache.checkRolePermission('admin', 'POST/api/user/add');
483
+ const hasPermission = await befly.cache.checkRolePermission("admin", "POST/api/user/add");
493
484
  // 返回: true 或 false
494
485
  ```
495
486
 
@@ -499,7 +490,7 @@ const hasPermission = await befly.cache.checkRolePermission('admin', 'POST/api/u
499
490
 
500
491
  ```typescript
501
492
  // 删除指定角色的权限缓存
502
- await befly.cache.deleteRolePermissions('admin');
493
+ await befly.cache.deleteRolePermissions("admin");
503
494
 
504
495
  // 重新缓存所有角色权限
505
496
  await befly.cache.cacheRolePermissions();
@@ -515,7 +506,7 @@ Bun Redis 客户端自动将多个并发请求合并为 pipeline,无需手动
515
506
 
516
507
  ```typescript
517
508
  // 这些请求会自动合并为一个 pipeline
518
- const [user1, user2, user3] = await Promise.all([befly.redis.getObject('user:1'), befly.redis.getObject('user:2'), befly.redis.getObject('user:3')]);
509
+ const [user1, user2, user3] = await Promise.all([befly.redis.getObject("user:1"), befly.redis.getObject("user:2"), befly.redis.getObject("user:3")]);
519
510
  ```
520
511
 
521
512
  ### 2. 使用批量方法
@@ -524,7 +515,7 @@ const [user1, user2, user3] = await Promise.all([befly.redis.getObject('user:1')
524
515
 
525
516
  ```typescript
526
517
  // ✅ 推荐:使用批量方法
527
- const users = await befly.redis.getBatch(['user:1', 'user:2', 'user:3']);
518
+ const users = await befly.redis.getBatch(["user:1", "user:2", "user:3"]);
528
519
 
529
520
  // ❌ 不推荐:循环调用
530
521
  const users = [];
@@ -537,20 +528,20 @@ for (const id of [1, 2, 3]) {
537
528
 
538
529
  ```typescript
539
530
  // 高频访问、变化少的数据 - 较长 TTL
540
- await befly.redis.setObject('config:system', config, 86400); // 24小时
531
+ await befly.redis.setObject("config:system", config, 86400); // 24小时
541
532
 
542
533
  // 实时性要求高的数据 - 较短 TTL
543
- await befly.redis.setObject('stats:online', count, 60); // 1分钟
534
+ await befly.redis.setObject("stats:online", count, 60); // 1分钟
544
535
 
545
536
  // 永久缓存(慎用)
546
- await befly.redis.setObject('constants:provinces', provinces); // 无 TTL
537
+ await befly.redis.setObject("constants:provinces", provinces); // 无 TTL
547
538
  ```
548
539
 
549
540
  ### 4. 避免大 Key
550
541
 
551
542
  ```typescript
552
543
  // ❌ 避免:存储大量数据在单个 key
553
- await befly.redis.setObject('all:users', hugeUserList); // 可能有 10MB+
544
+ await befly.redis.setObject("all:users", hugeUserList); // 可能有 10MB+
554
545
 
555
546
  // ✅ 推荐:分散存储
556
547
  for (const user of users) {
@@ -576,9 +567,9 @@ const pong = await befly.redis.ping();
576
567
 
577
568
  ```typescript
578
569
  // 操作失败时返回默认值,不会中断程序
579
- const value = await befly.redis.getObject('key'); // 返回 null
580
- const exists = await befly.redis.exists('key'); // 返回 false
581
- const count = await befly.redis.del('key'); // 返回 0
570
+ const value = await befly.redis.getObject("key"); // 返回 null
571
+ const exists = await befly.redis.exists("key"); // 返回 false
572
+ const count = await befly.redis.del("key"); // 返回 0
582
573
 
583
574
  // 错误会记录到日志
584
575
  // Logger.error('Redis getObject 错误', error);
@@ -587,9 +578,9 @@ const count = await befly.redis.del('key'); // 返回 0
587
578
  如需捕获错误,可以检查返回值:
588
579
 
589
580
  ```typescript
590
- const result = await befly.redis.setObject('key', data);
581
+ const result = await befly.redis.setObject("key", data);
591
582
  if (result === null) {
592
- Logger.warn('缓存写入失败');
583
+ Logger.warn("缓存写入失败");
593
584
  // 降级处理...
594
585
  }
595
586
  ```
@@ -0,0 +1,61 @@
1
+ # Cipher 加密工具
2
+
3
+ > 哈希、签名、密码加密与 JWT 令牌
4
+
5
+ ## 目录
6
+
7
+ - [概述](#概述)
8
+ - [Cipher 加密类](#cipher-加密类)
9
+ - [JWT 令牌](#jwt-令牌)
10
+ - [插件集成](#插件集成)
11
+ - [FAQ](#faq)
12
+
13
+ ---
14
+
15
+ ## 概述
16
+
17
+ Befly 提供两个安全相关的工具:
18
+
19
+ | 工具 | 说明 | 典型场景 |
20
+ | -------- | ---------- | ------------------------ |
21
+ | `Cipher` | 加密工具类 | 数据哈希、签名、密码加密 |
22
+ | `Jwt` | JWT 令牌类 | 用户认证、API 授权 |
23
+
24
+ ---
25
+
26
+ ## Cipher 加密类
27
+
28
+ `Cipher` 提供:
29
+
30
+ - 哈希:`md5/sha1/sha256/sha384/sha512/blake2b*`
31
+ - HMAC:`hmacSha256` 等
32
+ - 密码:`hashPassword`(bcrypt)与 `verifyPassword`
33
+ - 辅助:Base64、随机串、文件哈希、fastHash
34
+
35
+ ---
36
+
37
+ ## JWT 令牌
38
+
39
+ `Jwt` 用于签发与验证 JWT:
40
+
41
+ - `sign(payload, options?)`
42
+ - `verify(token, options?)`
43
+ - `decode(token, full?)`
44
+
45
+ JWT 插件会读取配置文件中的 `auth` 配置(如 secret / expiresIn / algorithm)。
46
+
47
+ ---
48
+
49
+ ## 插件集成
50
+
51
+ 在 API handler 中一般直接使用:
52
+
53
+ - `befly.cipher.*`
54
+ - `befly.jwt.*`
55
+
56
+ ---
57
+
58
+ ## FAQ
59
+
60
+ - 密码存储应使用 `hashPassword`(bcrypt),不要用 MD5/SHA\* 直接存密码。
61
+ - 生产环境必须替换 `auth.secret` 为强随机字符串。
@@ -0,0 +1,128 @@
1
+ # Befly 数据库操作指南
2
+
3
+ > 本文档详细介绍 Befly 框架的数据库操作 API,包括 CRUD 操作、事务、条件查询等。
4
+
5
+ ## 目录
6
+
7
+ - [核心概念](#核心概念)
8
+ - [字段命名规范](#字段命名规范)
9
+ - [查询方法](#查询方法)
10
+ - [写入方法](#写入方法)
11
+ - [数值操作](#数值操作)
12
+ - [事务操作](#事务操作)
13
+ - [多表联查](#多表联查)
14
+ - [Where 条件语法](#where-条件语法)
15
+ - [字段选择语法](#字段选择语法)
16
+ - [排序语法](#排序语法)
17
+ - [系统字段说明](#系统字段说明)
18
+ - [完整示例](#完整示例)
19
+
20
+ ---
21
+
22
+ ## 核心概念
23
+
24
+ ### DbHelper
25
+
26
+ `DbHelper` 是 Befly 的数据库操作核心类,提供了完整的 CRUD 封装。通过 `befly.db` 访问。
27
+
28
+ ```typescript
29
+ // 在 API handler 中使用
30
+ handler: async (befly, ctx) => {
31
+ const user = await befly.db.getOne({
32
+ table: "user",
33
+ where: { id: 1 }
34
+ });
35
+ };
36
+ ```
37
+
38
+ ### 自动转换
39
+
40
+ - **表名**:小驼峰 `userProfile` 自动转换为下划线 `user_profile`
41
+ - **字段名**:写入时小驼峰转下划线,查询时下划线转小驼峰
42
+ - **BIGINT 字段**:`id`、`*Id`、`*_id`、`*At`、`*_at` 自动转为 number
43
+
44
+ ### 自动过滤 null 和 undefined
45
+
46
+ 所有写入方法(`insData`、`insBatch`、`updData`)和条件查询(`where`)都会**自动过滤值为 `null` 或 `undefined` 的字段**。
47
+
48
+ ---
49
+
50
+ ## 字段命名规范
51
+
52
+ | 位置 | 格式 | 示例 |
53
+ | --------------------- | ------ | ----------------------- |
54
+ | 代码中(参数/返回值) | 小驼峰 | `userId`, `createdAt` |
55
+ | 数据库中 | 下划线 | `user_id`, `created_at` |
56
+
57
+ ---
58
+
59
+ ## 查询方法
60
+
61
+ - `getOne`:查询单条
62
+ - `getList`:分页查询
63
+ - `getAll`:查询全部(有上限保护)
64
+ - `getCount`:查询数量
65
+ - `exists`:检查存在
66
+ - `getFieldValue`:查询单字段
67
+
68
+ ---
69
+
70
+ ## 写入方法
71
+
72
+ - `insData`:插入单条(自动生成系统字段)
73
+ - `insBatch`:批量插入
74
+ - `updData`:更新(自动更新 `updated_at`)
75
+ - `delData`:软删除
76
+ - `delForce`:硬删除
77
+ - `disableData` / `enableData`:禁用/启用
78
+
79
+ ---
80
+
81
+ ## 数值操作
82
+
83
+ - `increment`:自增
84
+ - `decrement`:自减
85
+
86
+ ---
87
+
88
+ ## 事务操作
89
+
90
+ 使用 `trans` 方法执行事务,自动处理 commit/rollback。
91
+
92
+ ---
93
+
94
+ ## 多表联查
95
+
96
+ 查询方法支持通过 `joins` 参数进行多表联查。
97
+
98
+ ---
99
+
100
+ ## Where 条件语法
101
+
102
+ 支持 `$or/$and` 与 `$gt/$gte/$lt/$lte/$in/$between/$null/$like` 等。
103
+
104
+ ---
105
+
106
+ ## 字段选择语法
107
+
108
+ - `fields: []` / 不传:查询所有字段
109
+ - `fields: ["id", "name"]`:指定字段
110
+ - `fields: ["!password"]`:排除字段(不能混用包含与排除)
111
+
112
+ ---
113
+
114
+ ## 排序语法
115
+
116
+ 使用 `字段#方向` 格式:`ASC` / `DESC`。
117
+
118
+ ---
119
+
120
+ ## 系统字段说明
121
+
122
+ 每条记录自动包含:`id`、`created_at`、`updated_at`、`deleted_at`、`state`。
123
+
124
+ ---
125
+
126
+ ## 完整示例
127
+
128
+ 更完整示例与细节说明请参考仓库中的 `DbHelper` 相关测试用例与源码实现。