befly 3.9.37 → 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.
- package/README.md +38 -39
- package/befly.config.ts +62 -40
- package/checks/checkApi.ts +16 -16
- package/checks/checkApp.ts +19 -25
- package/checks/checkTable.ts +42 -42
- package/docs/README.md +42 -35
- package/docs/{api.md → api/api.md} +225 -235
- package/docs/cipher.md +71 -69
- package/docs/database.md +155 -153
- package/docs/{examples.md → guide/examples.md} +181 -181
- package/docs/guide/quickstart.md +331 -0
- package/docs/hooks/auth.md +38 -0
- package/docs/hooks/cors.md +28 -0
- package/docs/{hook.md → hooks/hook.md} +140 -57
- package/docs/hooks/parser.md +19 -0
- package/docs/hooks/rateLimit.md +47 -0
- package/docs/{redis.md → infra/redis.md} +84 -93
- package/docs/plugins/cipher.md +61 -0
- package/docs/plugins/database.md +128 -0
- package/docs/{plugin.md → plugins/plugin.md} +83 -81
- package/docs/quickstart.md +26 -26
- package/docs/{addon.md → reference/addon.md} +46 -46
- package/docs/{config.md → reference/config.md} +32 -80
- package/docs/{logger.md → reference/logger.md} +52 -52
- package/docs/{sync.md → reference/sync.md} +32 -35
- package/docs/{table.md → reference/table.md} +7 -7
- package/docs/{validator.md → reference/validator.md} +57 -57
- package/hooks/auth.ts +8 -4
- package/hooks/cors.ts +13 -13
- package/hooks/parser.ts +37 -17
- package/hooks/permission.ts +26 -14
- package/hooks/rateLimit.ts +276 -0
- package/hooks/validator.ts +15 -7
- package/lib/asyncContext.ts +43 -0
- package/lib/cacheHelper.ts +212 -81
- package/lib/cacheKeys.ts +38 -0
- package/lib/cipher.ts +30 -30
- package/lib/connect.ts +28 -28
- package/lib/dbHelper.ts +211 -109
- package/lib/jwt.ts +16 -16
- package/lib/logger.ts +610 -19
- package/lib/redisHelper.ts +185 -44
- package/lib/sqlBuilder.ts +90 -91
- package/lib/validator.ts +59 -39
- package/loader/loadApis.ts +53 -47
- package/loader/loadHooks.ts +40 -14
- package/loader/loadPlugins.ts +16 -17
- package/main.ts +57 -47
- package/package.json +47 -45
- package/paths.ts +15 -14
- package/plugins/cache.ts +5 -4
- package/plugins/cipher.ts +3 -3
- package/plugins/config.ts +2 -2
- package/plugins/db.ts +9 -9
- package/plugins/jwt.ts +3 -3
- package/plugins/logger.ts +8 -12
- package/plugins/redis.ts +8 -8
- package/plugins/tool.ts +6 -6
- package/router/api.ts +85 -56
- package/router/static.ts +12 -12
- package/sync/syncAll.ts +12 -12
- package/sync/syncApi.ts +55 -54
- package/sync/syncDb/apply.ts +20 -19
- package/sync/syncDb/constants.ts +25 -23
- package/sync/syncDb/ddl.ts +35 -36
- package/sync/syncDb/helpers.ts +6 -9
- package/sync/syncDb/schema.ts +10 -9
- package/sync/syncDb/sqlite.ts +7 -8
- package/sync/syncDb/table.ts +37 -35
- package/sync/syncDb/tableCreate.ts +21 -20
- package/sync/syncDb/types.ts +23 -20
- package/sync/syncDb/version.ts +10 -10
- package/sync/syncDb.ts +43 -36
- package/sync/syncDev.ts +74 -66
- package/sync/syncMenu.ts +190 -57
- package/tests/api-integration-array-number.test.ts +282 -0
- package/tests/befly-config-env.test.ts +78 -0
- package/tests/cacheHelper.test.ts +135 -104
- package/tests/cacheKeys.test.ts +41 -0
- package/tests/cipher.test.ts +90 -89
- package/tests/dbHelper-advanced.test.ts +140 -134
- package/tests/dbHelper-all-array-types.test.ts +316 -0
- package/tests/dbHelper-array-serialization.test.ts +258 -0
- package/tests/dbHelper-columns.test.ts +56 -55
- package/tests/dbHelper-execute.test.ts +45 -44
- package/tests/dbHelper-joins.test.ts +124 -119
- package/tests/fields-redis-cache.test.ts +29 -27
- package/tests/fields-validate.test.ts +38 -38
- package/tests/getClientIp.test.ts +54 -0
- package/tests/integration.test.ts +69 -67
- package/tests/jwt.test.ts +27 -26
- package/tests/logger.test.ts +267 -34
- package/tests/rateLimit-hook.test.ts +477 -0
- package/tests/redisHelper.test.ts +187 -188
- package/tests/redisKeys.test.ts +6 -73
- package/tests/scanConfig.test.ts +144 -0
- package/tests/sqlBuilder-advanced.test.ts +217 -215
- package/tests/sqlBuilder.test.ts +92 -91
- package/tests/sync-connection.test.ts +29 -29
- package/tests/syncDb-apply.test.ts +97 -96
- package/tests/syncDb-array-number.test.ts +160 -0
- package/tests/syncDb-constants.test.ts +48 -47
- package/tests/syncDb-ddl.test.ts +99 -98
- package/tests/syncDb-helpers.test.ts +29 -28
- package/tests/syncDb-schema.test.ts +61 -60
- package/tests/syncDb-types.test.ts +60 -59
- package/tests/syncMenu-paths.test.ts +68 -0
- package/tests/util.test.ts +42 -41
- package/tests/validator-array-number.test.ts +310 -0
- package/tests/validator-default.test.ts +373 -0
- package/tests/validator.test.ts +271 -266
- package/tsconfig.json +4 -5
- package/types/api.d.ts +7 -12
- package/types/befly.d.ts +60 -13
- package/types/cache.d.ts +8 -4
- package/types/common.d.ts +17 -9
- package/types/context.d.ts +2 -2
- package/types/crypto.d.ts +23 -0
- package/types/database.d.ts +19 -19
- package/types/hook.d.ts +2 -2
- package/types/jwt.d.ts +118 -0
- package/types/logger.d.ts +30 -0
- package/types/plugin.d.ts +4 -4
- package/types/redis.d.ts +7 -3
- package/types/roleApisCache.ts +23 -0
- package/types/sync.d.ts +10 -10
- package/types/table.d.ts +50 -9
- package/types/validate.d.ts +69 -0
- package/utils/addonHelper.ts +90 -0
- package/utils/arrayKeysToCamel.ts +18 -0
- package/utils/calcPerfTime.ts +13 -0
- package/utils/configTypes.ts +3 -0
- package/utils/cors.ts +19 -0
- package/utils/fieldClear.ts +75 -0
- package/utils/genShortId.ts +12 -0
- package/utils/getClientIp.ts +45 -0
- package/utils/keysToCamel.ts +22 -0
- package/utils/keysToSnake.ts +22 -0
- package/utils/modules.ts +98 -0
- package/utils/pickFields.ts +19 -0
- package/utils/process.ts +56 -0
- package/utils/regex.ts +225 -0
- package/utils/response.ts +115 -0
- package/utils/route.ts +23 -0
- package/utils/scanConfig.ts +142 -0
- package/utils/scanFiles.ts +48 -0
- package/.prettierignore +0 -2
- package/.prettierrc +0 -12
- package/docs/1-/345/237/272/346/234/254/344/273/213/347/273/215.md +0 -35
- package/docs/2-/345/210/235/346/255/245/344/275/223/351/252/214.md +0 -64
- package/docs/3-/347/254/254/344/270/200/344/270/252/346/216/245/345/217/243.md +0 -46
- package/docs/4-/346/223/215/344/275/234/346/225/260/346/215/256/345/272/223.md +0 -172
- package/hooks/requestLogger.ts +0 -84
- package/types/index.ts +0 -24
- 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(
|
|
35
|
+
await befly.redis.setObject("user:1", { name: "张三", age: 25 });
|
|
36
36
|
|
|
37
37
|
// 获取缓存
|
|
38
|
-
const user = await befly.redis.getObject(
|
|
38
|
+
const user = await befly.redis.getObject("user:1");
|
|
39
39
|
|
|
40
|
-
return befly.tool.Yes(
|
|
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(
|
|
55
|
+
await befly.redis.setString("key", "value");
|
|
56
56
|
|
|
57
57
|
// 带过期时间(秒)
|
|
58
|
-
await befly.redis.setString(
|
|
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(
|
|
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(
|
|
76
|
+
await befly.redis.setObject("user:1", {
|
|
77
77
|
id: 1,
|
|
78
|
-
name:
|
|
79
|
-
roles: [
|
|
78
|
+
name: "张三",
|
|
79
|
+
roles: ["admin", "user"]
|
|
80
80
|
});
|
|
81
81
|
|
|
82
82
|
// 带过期时间(秒)
|
|
83
|
-
await befly.redis.setObject(
|
|
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>(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
138
|
+
await befly.redis.sadd("tags:article:1", ["技术"]);
|
|
139
139
|
|
|
140
140
|
// 添加多个成员
|
|
141
|
-
await befly.redis.sadd(
|
|
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(
|
|
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(
|
|
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(
|
|
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:
|
|
176
|
-
{ key:
|
|
177
|
-
{ key:
|
|
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>([
|
|
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([
|
|
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([
|
|
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([
|
|
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:
|
|
215
|
-
{ key:
|
|
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:
|
|
225
|
-
{ key:
|
|
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:
|
|
235
|
-
{ key:
|
|
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:
|
|
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
|
-
###
|
|
280
|
+
### CacheKeys - 统一键名管理
|
|
281
281
|
|
|
282
282
|
避免硬编码,统一管理所有缓存键。
|
|
283
283
|
|
|
284
284
|
```typescript
|
|
285
|
-
import {
|
|
285
|
+
import { CacheKeys } from "befly/lib/cacheKeys";
|
|
286
286
|
|
|
287
287
|
// 获取键名
|
|
288
|
-
const key =
|
|
289
|
-
const key =
|
|
290
|
-
const key =
|
|
291
|
-
const key =
|
|
292
|
-
const key =
|
|
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
|
-
|
|
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
|
|
331
|
-
// ✅ Redis 缓存命中
|
|
332
|
-
```
|
|
322
|
+
const key = `ratelimit:${ctx.ip}:${ctx.route}`;
|
|
323
|
+
const count = await befly.redis.incrWithExpire(key, windowSeconds);
|
|
333
324
|
|
|
334
|
-
|
|
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
|
-
|
|
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(
|
|
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,
|
|
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(
|
|
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,
|
|
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,
|
|
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 =
|
|
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:
|
|
443
|
-
fields: [
|
|
444
|
-
orderBy: [
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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([
|
|
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(
|
|
531
|
+
await befly.redis.setObject("config:system", config, 86400); // 24小时
|
|
541
532
|
|
|
542
533
|
// 实时性要求高的数据 - 较短 TTL
|
|
543
|
-
await befly.redis.setObject(
|
|
534
|
+
await befly.redis.setObject("stats:online", count, 60); // 1分钟
|
|
544
535
|
|
|
545
536
|
// 永久缓存(慎用)
|
|
546
|
-
await befly.redis.setObject(
|
|
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(
|
|
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(
|
|
580
|
-
const exists = await befly.redis.exists(
|
|
581
|
-
const count = await befly.redis.del(
|
|
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(
|
|
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` 相关测试用例与源码实现。
|