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.
- package/README.md +37 -38
- 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} +223 -231
- package/docs/cipher.md +71 -69
- package/docs/database.md +143 -141
- 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} +1 -1
- 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 +7 -7
- package/lib/asyncContext.ts +43 -0
- package/lib/cacheHelper.ts +212 -77
- package/lib/cacheKeys.ts +38 -0
- package/lib/cipher.ts +30 -30
- package/lib/connect.ts +28 -28
- package/lib/dbHelper.ts +183 -102
- 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 +48 -44
- 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 -52
- 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 -65
- package/sync/syncMenu.ts +190 -55
- 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
package/lib/cacheHelper.ts
CHANGED
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
* 负责在服务器启动时缓存接口、菜单和角色权限到 Redis
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
import { RedisKeys } from 'befly-shared/redisKeys';
|
|
6
|
+
import type { BeflyContext } from "../types/befly.js";
|
|
8
7
|
|
|
9
|
-
import
|
|
8
|
+
import { makeRouteKey } from "../utils/route.js";
|
|
9
|
+
import { CacheKeys } from "./cacheKeys.js";
|
|
10
|
+
import { Logger } from "./logger.js";
|
|
10
11
|
|
|
11
12
|
/**
|
|
12
13
|
* 缓存助手类
|
|
@@ -14,6 +15,104 @@ import type { BeflyContext } from '../types/befly.js';
|
|
|
14
15
|
export class CacheHelper {
|
|
15
16
|
private befly: BeflyContext;
|
|
16
17
|
|
|
18
|
+
private static readonly API_ID_IN_CHUNK_SIZE = 1000;
|
|
19
|
+
|
|
20
|
+
private normalizeNumberIdList(value: unknown): number[] {
|
|
21
|
+
if (value === null || value === undefined) return [];
|
|
22
|
+
|
|
23
|
+
const normalizeSingle = (item: unknown): number | null => {
|
|
24
|
+
if (typeof item === "number") {
|
|
25
|
+
if (!Number.isFinite(item)) return null;
|
|
26
|
+
const intValue = Math.trunc(item);
|
|
27
|
+
return Number.isSafeInteger(intValue) ? intValue : null;
|
|
28
|
+
}
|
|
29
|
+
if (typeof item === "bigint") {
|
|
30
|
+
const intValue = Number(item);
|
|
31
|
+
return Number.isSafeInteger(intValue) ? intValue : null;
|
|
32
|
+
}
|
|
33
|
+
if (typeof item === "string") {
|
|
34
|
+
const trimmed = item.trim();
|
|
35
|
+
if (!trimmed) return null;
|
|
36
|
+
const intValue = Number.parseInt(trimmed, 10);
|
|
37
|
+
return Number.isSafeInteger(intValue) ? intValue : null;
|
|
38
|
+
}
|
|
39
|
+
return null;
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
if (Array.isArray(value)) {
|
|
43
|
+
const ids: number[] = [];
|
|
44
|
+
for (const item of value) {
|
|
45
|
+
const id = normalizeSingle(item);
|
|
46
|
+
if (id !== null) ids.push(id);
|
|
47
|
+
}
|
|
48
|
+
return ids;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (typeof value === "string") {
|
|
52
|
+
const str = value.trim();
|
|
53
|
+
if (!str) return [];
|
|
54
|
+
|
|
55
|
+
if (str.startsWith("[")) {
|
|
56
|
+
try {
|
|
57
|
+
const parsed = JSON.parse(str);
|
|
58
|
+
return this.normalizeNumberIdList(parsed);
|
|
59
|
+
} catch {
|
|
60
|
+
// ignore
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const ids: number[] = [];
|
|
65
|
+
const parts = str.split(",");
|
|
66
|
+
for (const part of parts) {
|
|
67
|
+
const id = normalizeSingle(part);
|
|
68
|
+
if (id !== null) ids.push(id);
|
|
69
|
+
}
|
|
70
|
+
return ids;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const single = normalizeSingle(value);
|
|
74
|
+
return single === null ? [] : [single];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
private chunkNumberArray(arr: number[], chunkSize: number): number[][] {
|
|
78
|
+
if (chunkSize <= 0) {
|
|
79
|
+
throw new Error(`chunkSize 必须为正整数 (chunkSize: ${chunkSize})`);
|
|
80
|
+
}
|
|
81
|
+
if (arr.length === 0) return [];
|
|
82
|
+
|
|
83
|
+
const chunks: number[][] = [];
|
|
84
|
+
for (let i = 0; i < arr.length; i += chunkSize) {
|
|
85
|
+
chunks.push(arr.slice(i, i + chunkSize));
|
|
86
|
+
}
|
|
87
|
+
return chunks;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
private async buildApiPathMapByIds(apiIds: number[]): Promise<Map<number, string>> {
|
|
91
|
+
const uniqueIds = Array.from(new Set(apiIds));
|
|
92
|
+
if (uniqueIds.length === 0) {
|
|
93
|
+
return new Map();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
const apiMap = new Map<number, string>();
|
|
97
|
+
const chunks = this.chunkNumberArray(uniqueIds, CacheHelper.API_ID_IN_CHUNK_SIZE);
|
|
98
|
+
|
|
99
|
+
for (const chunk of chunks) {
|
|
100
|
+
const apis = await this.befly.db.getAll({
|
|
101
|
+
table: "addon_admin_api",
|
|
102
|
+
fields: ["id", "method", "path"],
|
|
103
|
+
where: {
|
|
104
|
+
id$in: chunk
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
for (const api of apis.lists) {
|
|
109
|
+
apiMap.set(api.id, makeRouteKey(api.method, api.path));
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return apiMap;
|
|
114
|
+
}
|
|
115
|
+
|
|
17
116
|
/**
|
|
18
117
|
* 构造函数
|
|
19
118
|
* @param befly - Befly 上下文
|
|
@@ -28,26 +127,25 @@ export class CacheHelper {
|
|
|
28
127
|
async cacheApis(): Promise<void> {
|
|
29
128
|
try {
|
|
30
129
|
// 检查表是否存在
|
|
31
|
-
const tableExists = await this.befly.db.tableExists(
|
|
130
|
+
const tableExists = await this.befly.db.tableExists("addon_admin_api");
|
|
32
131
|
if (!tableExists) {
|
|
33
|
-
Logger.warn(
|
|
132
|
+
Logger.warn("⚠️ 接口表不存在,跳过接口缓存");
|
|
34
133
|
return;
|
|
35
134
|
}
|
|
36
135
|
|
|
37
136
|
// 从数据库查询所有接口
|
|
38
137
|
const apiList = await this.befly.db.getAll({
|
|
39
|
-
table:
|
|
40
|
-
orderBy: ['addonName#ASC', 'path#ASC']
|
|
138
|
+
table: "addon_admin_api"
|
|
41
139
|
});
|
|
42
140
|
|
|
43
141
|
// 缓存到 Redis
|
|
44
|
-
const result = await this.befly.redis.setObject(
|
|
142
|
+
const result = await this.befly.redis.setObject(CacheKeys.apisAll(), apiList.lists);
|
|
45
143
|
|
|
46
144
|
if (result === null) {
|
|
47
|
-
Logger.warn(
|
|
145
|
+
Logger.warn("⚠️ 接口缓存失败");
|
|
48
146
|
}
|
|
49
147
|
} catch (error: any) {
|
|
50
|
-
Logger.error({ err: error },
|
|
148
|
+
Logger.error({ err: error }, "⚠️ 接口缓存异常");
|
|
51
149
|
}
|
|
52
150
|
}
|
|
53
151
|
|
|
@@ -57,102 +155,139 @@ export class CacheHelper {
|
|
|
57
155
|
async cacheMenus(): Promise<void> {
|
|
58
156
|
try {
|
|
59
157
|
// 检查表是否存在
|
|
60
|
-
const tableExists = await this.befly.db.tableExists(
|
|
158
|
+
const tableExists = await this.befly.db.tableExists("addon_admin_menu");
|
|
61
159
|
if (!tableExists) {
|
|
62
|
-
Logger.warn(
|
|
160
|
+
Logger.warn("⚠️ 菜单表不存在,跳过菜单缓存");
|
|
63
161
|
return;
|
|
64
162
|
}
|
|
65
163
|
|
|
66
164
|
// 从数据库查询所有菜单
|
|
67
165
|
const menus = await this.befly.db.getAll({
|
|
68
|
-
table:
|
|
69
|
-
orderBy: ['sort#ASC', 'id#ASC']
|
|
166
|
+
table: "addon_admin_menu"
|
|
70
167
|
});
|
|
71
168
|
|
|
72
169
|
// 缓存到 Redis
|
|
73
|
-
const result = await this.befly.redis.setObject(
|
|
170
|
+
const result = await this.befly.redis.setObject(CacheKeys.menusAll(), menus.lists);
|
|
74
171
|
|
|
75
172
|
if (result === null) {
|
|
76
|
-
Logger.warn(
|
|
173
|
+
Logger.warn("⚠️ 菜单缓存失败");
|
|
77
174
|
}
|
|
78
175
|
} catch (error: any) {
|
|
79
|
-
Logger.warn({ err: error },
|
|
176
|
+
Logger.warn({ err: error }, "⚠️ 菜单缓存异常");
|
|
80
177
|
}
|
|
81
178
|
}
|
|
82
179
|
|
|
83
180
|
/**
|
|
84
181
|
* 缓存所有角色的接口权限到 Redis
|
|
85
|
-
*
|
|
182
|
+
* 全量重建:清理所有角色权限缓存并重建
|
|
183
|
+
* - 极简方案:每个角色一个 Set,直接覆盖更新(DEL + SADD)
|
|
86
184
|
*/
|
|
87
|
-
async
|
|
185
|
+
async rebuildRoleApiPermissions(): Promise<void> {
|
|
88
186
|
try {
|
|
89
187
|
// 检查表是否存在
|
|
90
|
-
const apiTableExists = await this.befly.db.tableExists(
|
|
91
|
-
const roleTableExists = await this.befly.db.tableExists(
|
|
188
|
+
const apiTableExists = await this.befly.db.tableExists("addon_admin_api");
|
|
189
|
+
const roleTableExists = await this.befly.db.tableExists("addon_admin_role");
|
|
92
190
|
|
|
93
191
|
if (!apiTableExists || !roleTableExists) {
|
|
94
|
-
Logger.warn(
|
|
192
|
+
Logger.warn("⚠️ 接口或角色表不存在,跳过角色权限缓存");
|
|
95
193
|
return;
|
|
96
194
|
}
|
|
97
195
|
|
|
98
|
-
//
|
|
99
|
-
const
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
]);
|
|
196
|
+
// 查询所有角色(仅取必要字段)
|
|
197
|
+
const roles = await this.befly.db.getAll({
|
|
198
|
+
table: "addon_admin_role",
|
|
199
|
+
fields: ["code", "apis"]
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
const roleApiIdsMap = new Map<string, number[]>();
|
|
203
|
+
const allApiIdsSet = new Set<number>();
|
|
107
204
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
205
|
+
for (const role of roles.lists) {
|
|
206
|
+
if (!role?.code) continue;
|
|
207
|
+
const apiIds = this.normalizeNumberIdList(role.apis);
|
|
208
|
+
roleApiIdsMap.set(role.code, apiIds);
|
|
209
|
+
for (const id of apiIds) {
|
|
210
|
+
allApiIdsSet.add(id);
|
|
211
|
+
}
|
|
112
212
|
}
|
|
113
213
|
|
|
114
|
-
|
|
115
|
-
|
|
214
|
+
const roleCodes = Array.from(roleApiIdsMap.keys());
|
|
215
|
+
if (roleCodes.length === 0) {
|
|
216
|
+
Logger.info("✅ 没有需要缓存的角色权限");
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
116
219
|
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
220
|
+
// 构建所有需要的 API 映射(按 ID 分块使用 $in,避免全表扫描/避免超长 IN)
|
|
221
|
+
const allApiIds = Array.from(allApiIdsSet);
|
|
222
|
+
const apiMap = await this.buildApiPathMapByIds(allApiIds);
|
|
223
|
+
|
|
224
|
+
// 清理所有角色的缓存 key(保证幂等)
|
|
225
|
+
const roleKeys = roleCodes.map((code) => CacheKeys.roleApis(code));
|
|
226
|
+
await this.befly.redis.delBatch(roleKeys);
|
|
227
|
+
|
|
228
|
+
// 批量写入新缓存(只写入非空权限)
|
|
229
|
+
const items: Array<{ key: string; members: string[] }> = [];
|
|
230
|
+
|
|
231
|
+
for (const roleCode of roleCodes) {
|
|
232
|
+
const apiIds = roleApiIdsMap.get(roleCode) || [];
|
|
233
|
+
const membersSet = new Set<string>();
|
|
234
|
+
|
|
235
|
+
for (const id of apiIds) {
|
|
236
|
+
const apiPath = apiMap.get(id);
|
|
237
|
+
if (apiPath) {
|
|
238
|
+
membersSet.add(apiPath);
|
|
131
239
|
}
|
|
132
240
|
}
|
|
133
241
|
|
|
134
|
-
|
|
135
|
-
|
|
242
|
+
const members = Array.from(membersSet).sort();
|
|
243
|
+
|
|
244
|
+
if (members.length > 0) {
|
|
245
|
+
items.push({ key: CacheKeys.roleApis(roleCode), members: members });
|
|
136
246
|
}
|
|
137
247
|
}
|
|
138
248
|
|
|
139
|
-
if (
|
|
140
|
-
|
|
141
|
-
return;
|
|
249
|
+
if (items.length > 0) {
|
|
250
|
+
await this.befly.redis.saddBatch(items);
|
|
142
251
|
}
|
|
143
252
|
|
|
144
|
-
//
|
|
145
|
-
|
|
146
|
-
|
|
253
|
+
// 极简方案不做版本/ready/meta:重建完成即生效
|
|
254
|
+
} catch (error: any) {
|
|
255
|
+
Logger.warn({ err: error }, "⚠️ 角色权限缓存异常");
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
/**
|
|
260
|
+
* 增量刷新单个角色的接口权限缓存
|
|
261
|
+
* - apiIds 为空数组:仅清理缓存(防止残留)
|
|
262
|
+
* - apiIds 非空:使用 $in 最小查询,DEL 后 SADD
|
|
263
|
+
*/
|
|
264
|
+
async refreshRoleApiPermissions(roleCode: string, apiIds: number[]): Promise<void> {
|
|
265
|
+
if (!roleCode || typeof roleCode !== "string") {
|
|
266
|
+
throw new Error("roleCode 必须是非空字符串");
|
|
267
|
+
}
|
|
268
|
+
const normalizedIds = this.normalizeNumberIdList(apiIds);
|
|
269
|
+
const roleKey = CacheKeys.roleApis(roleCode);
|
|
270
|
+
|
|
271
|
+
// 空数组短路:避免触发 $in 空数组异常,同时保证清理残留
|
|
272
|
+
if (normalizedIds.length === 0) {
|
|
273
|
+
await this.befly.redis.del(roleKey);
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
147
276
|
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
277
|
+
const apiMap = await this.buildApiPathMapByIds(normalizedIds);
|
|
278
|
+
const membersSet = new Set<string>();
|
|
279
|
+
for (const id of normalizedIds) {
|
|
280
|
+
const apiPath = apiMap.get(id);
|
|
281
|
+
if (apiPath) {
|
|
282
|
+
membersSet.add(apiPath);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
151
285
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
286
|
+
const members = Array.from(membersSet);
|
|
287
|
+
|
|
288
|
+
await this.befly.redis.del(roleKey);
|
|
289
|
+
if (members.length > 0) {
|
|
290
|
+
await this.befly.redis.sadd(roleKey, members);
|
|
156
291
|
}
|
|
157
292
|
}
|
|
158
293
|
|
|
@@ -167,7 +302,7 @@ export class CacheHelper {
|
|
|
167
302
|
await this.cacheMenus();
|
|
168
303
|
|
|
169
304
|
// 3. 缓存角色权限
|
|
170
|
-
await this.
|
|
305
|
+
await this.rebuildRoleApiPermissions();
|
|
171
306
|
}
|
|
172
307
|
|
|
173
308
|
/**
|
|
@@ -176,10 +311,10 @@ export class CacheHelper {
|
|
|
176
311
|
*/
|
|
177
312
|
async getApis(): Promise<any[]> {
|
|
178
313
|
try {
|
|
179
|
-
const apis = await this.befly.redis.getObject<any[]>(
|
|
314
|
+
const apis = await this.befly.redis.getObject<any[]>(CacheKeys.apisAll());
|
|
180
315
|
return apis || [];
|
|
181
316
|
} catch (error: any) {
|
|
182
|
-
Logger.error({ err: error },
|
|
317
|
+
Logger.error({ err: error }, "获取接口缓存失败");
|
|
183
318
|
return [];
|
|
184
319
|
}
|
|
185
320
|
}
|
|
@@ -190,10 +325,10 @@ export class CacheHelper {
|
|
|
190
325
|
*/
|
|
191
326
|
async getMenus(): Promise<any[]> {
|
|
192
327
|
try {
|
|
193
|
-
const menus = await this.befly.redis.getObject<any[]>(
|
|
328
|
+
const menus = await this.befly.redis.getObject<any[]>(CacheKeys.menusAll());
|
|
194
329
|
return menus || [];
|
|
195
330
|
} catch (error: any) {
|
|
196
|
-
Logger.error({ err: error },
|
|
331
|
+
Logger.error({ err: error }, "获取菜单缓存失败");
|
|
197
332
|
return [];
|
|
198
333
|
}
|
|
199
334
|
}
|
|
@@ -205,10 +340,10 @@ export class CacheHelper {
|
|
|
205
340
|
*/
|
|
206
341
|
async getRolePermissions(roleCode: string): Promise<string[]> {
|
|
207
342
|
try {
|
|
208
|
-
const permissions = await this.befly.redis.smembers(
|
|
343
|
+
const permissions = await this.befly.redis.smembers(CacheKeys.roleApis(roleCode));
|
|
209
344
|
return permissions || [];
|
|
210
345
|
} catch (error: any) {
|
|
211
|
-
Logger.error({ err: error, roleCode: roleCode },
|
|
346
|
+
Logger.error({ err: error, roleCode: roleCode }, "获取角色权限缓存失败");
|
|
212
347
|
return [];
|
|
213
348
|
}
|
|
214
349
|
}
|
|
@@ -221,9 +356,9 @@ export class CacheHelper {
|
|
|
221
356
|
*/
|
|
222
357
|
async checkRolePermission(roleCode: string, apiPath: string): Promise<boolean> {
|
|
223
358
|
try {
|
|
224
|
-
return await this.befly.redis.sismember(
|
|
359
|
+
return await this.befly.redis.sismember(CacheKeys.roleApis(roleCode), apiPath);
|
|
225
360
|
} catch (error: any) {
|
|
226
|
-
Logger.error({ err: error, roleCode: roleCode },
|
|
361
|
+
Logger.error({ err: error, roleCode: roleCode }, "检查角色权限失败");
|
|
227
362
|
return false;
|
|
228
363
|
}
|
|
229
364
|
}
|
|
@@ -235,14 +370,14 @@ export class CacheHelper {
|
|
|
235
370
|
*/
|
|
236
371
|
async deleteRolePermissions(roleCode: string): Promise<boolean> {
|
|
237
372
|
try {
|
|
238
|
-
const result = await this.befly.redis.del(
|
|
373
|
+
const result = await this.befly.redis.del(CacheKeys.roleApis(roleCode));
|
|
239
374
|
if (result > 0) {
|
|
240
375
|
Logger.info(`✅ 已删除角色 ${roleCode} 的权限缓存`);
|
|
241
376
|
return true;
|
|
242
377
|
}
|
|
243
378
|
return false;
|
|
244
379
|
} catch (error: any) {
|
|
245
|
-
Logger.error({ err: error, roleCode: roleCode },
|
|
380
|
+
Logger.error({ err: error, roleCode: roleCode }, "删除角色权限缓存失败");
|
|
246
381
|
return false;
|
|
247
382
|
}
|
|
248
383
|
}
|
package/lib/cacheKeys.ts
ADDED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cache Key 统一管理
|
|
3
|
+
* 所有缓存键在此统一定义,避免硬编码分散
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Cache Key 生成函数集合
|
|
8
|
+
*/
|
|
9
|
+
export class CacheKeys {
|
|
10
|
+
/** 所有接口缓存 */
|
|
11
|
+
static apisAll(): string {
|
|
12
|
+
return "befly:apis:all";
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** 所有菜单缓存 */
|
|
16
|
+
static menusAll(): string {
|
|
17
|
+
return "befly:menus:all";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** 角色信息缓存(完整角色对象) */
|
|
21
|
+
static roleInfo(roleCode: string): string {
|
|
22
|
+
return `befly:role:info:${roleCode}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 角色接口权限缓存(Set 集合)
|
|
27
|
+
* - key: befly:role:apis:${roleCode}
|
|
28
|
+
* - member: METHOD/path
|
|
29
|
+
*/
|
|
30
|
+
static roleApis(roleCode: string): string {
|
|
31
|
+
return `befly:role:apis:${roleCode}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** 表结构缓存 */
|
|
35
|
+
static tableColumns(table: string): string {
|
|
36
|
+
return `befly:table:columns:${table}`;
|
|
37
|
+
}
|
|
38
|
+
}
|
package/lib/cipher.ts
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
|
|
1
|
+
/**
|
|
2
2
|
* 加密工具类 - TypeScript 版本
|
|
3
3
|
* 提供各种哈希、HMAC、密码加密等功能
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
6
|
+
import type { EncodingType, HashAlgorithm, PasswordHashOptions } from "../types/crypto.js";
|
|
7
7
|
|
|
8
|
-
import
|
|
8
|
+
import { createSign } from "node:crypto";
|
|
9
9
|
|
|
10
10
|
/**
|
|
11
11
|
* 加密工具类
|
|
@@ -17,8 +17,8 @@ export class Cipher {
|
|
|
17
17
|
* @param encoding - 输出编码
|
|
18
18
|
* @returns MD5 哈希值
|
|
19
19
|
*/
|
|
20
|
-
static md5(data: string | Uint8Array, encoding: EncodingType =
|
|
21
|
-
const hasher = new Bun.CryptoHasher(
|
|
20
|
+
static md5(data: string | Uint8Array, encoding: EncodingType = "hex"): string {
|
|
21
|
+
const hasher = new Bun.CryptoHasher("md5");
|
|
22
22
|
hasher.update(data);
|
|
23
23
|
return hasher.digest(encoding);
|
|
24
24
|
}
|
|
@@ -30,8 +30,8 @@ export class Cipher {
|
|
|
30
30
|
* @param encoding - 输出编码
|
|
31
31
|
* @returns HMAC-MD5 签名
|
|
32
32
|
*/
|
|
33
|
-
static hmacMd5(key: string | Uint8Array, data: string | Uint8Array, encoding: EncodingType =
|
|
34
|
-
const hasher = new Bun.CryptoHasher(
|
|
33
|
+
static hmacMd5(key: string | Uint8Array, data: string | Uint8Array, encoding: EncodingType = "hex"): string {
|
|
34
|
+
const hasher = new Bun.CryptoHasher("md5", key);
|
|
35
35
|
hasher.update(data);
|
|
36
36
|
return hasher.digest(encoding);
|
|
37
37
|
}
|
|
@@ -42,8 +42,8 @@ export class Cipher {
|
|
|
42
42
|
* @param encoding - 输出编码
|
|
43
43
|
* @returns SHA-1 哈希值
|
|
44
44
|
*/
|
|
45
|
-
static sha1(data: string | Uint8Array, encoding: EncodingType =
|
|
46
|
-
const hasher = new Bun.CryptoHasher(
|
|
45
|
+
static sha1(data: string | Uint8Array, encoding: EncodingType = "hex"): string {
|
|
46
|
+
const hasher = new Bun.CryptoHasher("sha1");
|
|
47
47
|
hasher.update(data);
|
|
48
48
|
return hasher.digest(encoding);
|
|
49
49
|
}
|
|
@@ -55,8 +55,8 @@ export class Cipher {
|
|
|
55
55
|
* @param encoding - 输出编码
|
|
56
56
|
* @returns HMAC-SHA1 签名
|
|
57
57
|
*/
|
|
58
|
-
static hmacSha1(key: string | Uint8Array, data: string | Uint8Array, encoding: EncodingType =
|
|
59
|
-
const hasher = new Bun.CryptoHasher(
|
|
58
|
+
static hmacSha1(key: string | Uint8Array, data: string | Uint8Array, encoding: EncodingType = "hex"): string {
|
|
59
|
+
const hasher = new Bun.CryptoHasher("sha1", key);
|
|
60
60
|
hasher.update(data);
|
|
61
61
|
return hasher.digest(encoding);
|
|
62
62
|
}
|
|
@@ -67,8 +67,8 @@ export class Cipher {
|
|
|
67
67
|
* @param encoding - 输出编码
|
|
68
68
|
* @returns SHA-256 哈希值
|
|
69
69
|
*/
|
|
70
|
-
static sha256(data: string | Uint8Array, encoding: EncodingType =
|
|
71
|
-
const hasher = new Bun.CryptoHasher(
|
|
70
|
+
static sha256(data: string | Uint8Array, encoding: EncodingType = "hex"): string {
|
|
71
|
+
const hasher = new Bun.CryptoHasher("sha256");
|
|
72
72
|
hasher.update(data);
|
|
73
73
|
return hasher.digest(encoding);
|
|
74
74
|
}
|
|
@@ -80,8 +80,8 @@ export class Cipher {
|
|
|
80
80
|
* @param encoding - 输出编码('hex' | 'base64' | 'base64url')
|
|
81
81
|
* @returns RSA-SHA256 签名
|
|
82
82
|
*/
|
|
83
|
-
static rsaSha256(data: string, privateKey: string | Buffer, encoding:
|
|
84
|
-
const sign = createSign(
|
|
83
|
+
static rsaSha256(data: string, privateKey: string | Buffer, encoding: "hex" | "base64" | "base64url" = "hex"): string {
|
|
84
|
+
const sign = createSign("RSA-SHA256");
|
|
85
85
|
sign.update(data);
|
|
86
86
|
const signature = sign.sign(privateKey, encoding);
|
|
87
87
|
return signature;
|
|
@@ -94,8 +94,8 @@ export class Cipher {
|
|
|
94
94
|
* @param encoding - 输出编码
|
|
95
95
|
* @returns HMAC-SHA256 签名
|
|
96
96
|
*/
|
|
97
|
-
static hmacSha256(key: string | Uint8Array, data: string | Uint8Array, encoding: EncodingType =
|
|
98
|
-
const hasher = new Bun.CryptoHasher(
|
|
97
|
+
static hmacSha256(key: string | Uint8Array, data: string | Uint8Array, encoding: EncodingType = "hex"): string {
|
|
98
|
+
const hasher = new Bun.CryptoHasher("sha256", key);
|
|
99
99
|
hasher.update(data);
|
|
100
100
|
return hasher.digest(encoding);
|
|
101
101
|
}
|
|
@@ -106,8 +106,8 @@ export class Cipher {
|
|
|
106
106
|
* @param encoding - 输出编码
|
|
107
107
|
* @returns SHA-512 哈希值
|
|
108
108
|
*/
|
|
109
|
-
static sha512(data: string | Uint8Array, encoding: EncodingType =
|
|
110
|
-
const hasher = new Bun.CryptoHasher(
|
|
109
|
+
static sha512(data: string | Uint8Array, encoding: EncodingType = "hex"): string {
|
|
110
|
+
const hasher = new Bun.CryptoHasher("sha512");
|
|
111
111
|
hasher.update(data);
|
|
112
112
|
return hasher.digest(encoding);
|
|
113
113
|
}
|
|
@@ -119,8 +119,8 @@ export class Cipher {
|
|
|
119
119
|
* @param encoding - 输出编码
|
|
120
120
|
* @returns HMAC-SHA512 签名
|
|
121
121
|
*/
|
|
122
|
-
static hmacSha512(key: string | Uint8Array, data: string | Uint8Array, encoding: EncodingType =
|
|
123
|
-
const hasher = new Bun.CryptoHasher(
|
|
122
|
+
static hmacSha512(key: string | Uint8Array, data: string | Uint8Array, encoding: EncodingType = "hex"): string {
|
|
123
|
+
const hasher = new Bun.CryptoHasher("sha512", key);
|
|
124
124
|
hasher.update(data);
|
|
125
125
|
return hasher.digest(encoding);
|
|
126
126
|
}
|
|
@@ -132,7 +132,7 @@ export class Cipher {
|
|
|
132
132
|
* @param encoding - 输出编码
|
|
133
133
|
* @returns 哈希值
|
|
134
134
|
*/
|
|
135
|
-
static hash(algorithm: HashAlgorithm, data: string | Uint8Array, encoding: EncodingType =
|
|
135
|
+
static hash(algorithm: HashAlgorithm, data: string | Uint8Array, encoding: EncodingType = "hex"): string {
|
|
136
136
|
const hasher = new Bun.CryptoHasher(algorithm);
|
|
137
137
|
hasher.update(data);
|
|
138
138
|
return hasher.digest(encoding);
|
|
@@ -146,7 +146,7 @@ export class Cipher {
|
|
|
146
146
|
* @param encoding - 输出编码
|
|
147
147
|
* @returns HMAC 签名
|
|
148
148
|
*/
|
|
149
|
-
static hmac(algorithm: HashAlgorithm, key: string | Uint8Array, data: string | Uint8Array, encoding: EncodingType =
|
|
149
|
+
static hmac(algorithm: HashAlgorithm, key: string | Uint8Array, data: string | Uint8Array, encoding: EncodingType = "hex"): string {
|
|
150
150
|
const hasher = new Bun.CryptoHasher(algorithm, key);
|
|
151
151
|
hasher.update(data);
|
|
152
152
|
return hasher.digest(encoding);
|
|
@@ -159,7 +159,7 @@ export class Cipher {
|
|
|
159
159
|
* @param encoding - 输出编码
|
|
160
160
|
* @returns 文件哈希值
|
|
161
161
|
*/
|
|
162
|
-
static async hashFile(filePath: string, algorithm: HashAlgorithm =
|
|
162
|
+
static async hashFile(filePath: string, algorithm: HashAlgorithm = "sha256", encoding: EncodingType = "hex"): Promise<string> {
|
|
163
163
|
const file = Bun.file(filePath);
|
|
164
164
|
const hasher = new Bun.CryptoHasher(algorithm);
|
|
165
165
|
|
|
@@ -192,7 +192,7 @@ export class Cipher {
|
|
|
192
192
|
*/
|
|
193
193
|
static async hashPassword(password: string, options: PasswordHashOptions = {}): Promise<string> {
|
|
194
194
|
const finalOptions = {
|
|
195
|
-
algorithm:
|
|
195
|
+
algorithm: "bcrypt",
|
|
196
196
|
...options
|
|
197
197
|
} as any;
|
|
198
198
|
return await Bun.password.hash(password, finalOptions);
|
|
@@ -219,7 +219,7 @@ export class Cipher {
|
|
|
219
219
|
* @returns Base64 编码的字符串
|
|
220
220
|
*/
|
|
221
221
|
static base64Encode(data: string): string {
|
|
222
|
-
return Buffer.from(data,
|
|
222
|
+
return Buffer.from(data, "utf8").toString("base64");
|
|
223
223
|
}
|
|
224
224
|
|
|
225
225
|
/**
|
|
@@ -228,7 +228,7 @@ export class Cipher {
|
|
|
228
228
|
* @returns 解码后的字符串
|
|
229
229
|
*/
|
|
230
230
|
static base64Decode(data: string): string {
|
|
231
|
-
return Buffer.from(data,
|
|
231
|
+
return Buffer.from(data, "base64").toString("utf8");
|
|
232
232
|
}
|
|
233
233
|
|
|
234
234
|
/**
|
|
@@ -239,9 +239,9 @@ export class Cipher {
|
|
|
239
239
|
static randomString(length: number): string {
|
|
240
240
|
const bytes = Math.ceil(length / 2);
|
|
241
241
|
const randomBytes = crypto.getRandomValues(new Uint8Array(bytes));
|
|
242
|
-
let result =
|
|
242
|
+
let result = "";
|
|
243
243
|
for (let i = 0; i < randomBytes.length; i++) {
|
|
244
|
-
result += randomBytes[i].toString(16).padStart(2,
|
|
244
|
+
result += randomBytes[i].toString(16).padStart(2, "0");
|
|
245
245
|
}
|
|
246
246
|
return result.slice(0, length);
|
|
247
247
|
}
|
|
@@ -254,6 +254,6 @@ export class Cipher {
|
|
|
254
254
|
*/
|
|
255
255
|
static fastHash(data: string | Uint8Array, seed: number = 0): number {
|
|
256
256
|
const result = Bun.hash(data, seed);
|
|
257
|
-
return typeof result ===
|
|
257
|
+
return typeof result === "bigint" ? Number(result) : result;
|
|
258
258
|
}
|
|
259
259
|
}
|