befly 3.16.11 → 3.17.2
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 +6 -0
- package/apis/admin/cacheRefresh.js +122 -0
- package/apis/admin/del.js +34 -0
- package/apis/admin/detail.js +23 -0
- package/apis/admin/ins.js +69 -0
- package/apis/admin/list.js +28 -0
- package/apis/admin/upd.js +95 -0
- package/apis/api/all.js +24 -0
- package/apis/api/list.js +31 -0
- package/apis/auth/login.js +123 -0
- package/apis/auth/sendSmsCode.js +24 -0
- package/apis/dashboard/configStatus.js +39 -0
- package/apis/dashboard/environmentInfo.js +43 -0
- package/apis/dashboard/performanceMetrics.js +20 -0
- package/apis/dashboard/permissionStats.js +27 -0
- package/apis/dashboard/serviceStatus.js +75 -0
- package/apis/dashboard/systemInfo.js +19 -0
- package/apis/dashboard/systemOverview.js +30 -0
- package/apis/dashboard/systemResources.js +106 -0
- package/apis/dict/all.js +23 -0
- package/apis/dict/del.js +16 -0
- package/apis/dict/detail.js +27 -0
- package/apis/dict/ins.js +51 -0
- package/apis/dict/items.js +30 -0
- package/apis/dict/list.js +36 -0
- package/apis/dict/upd.js +74 -0
- package/apis/dictType/all.js +16 -0
- package/apis/dictType/del.js +38 -0
- package/apis/dictType/detail.js +20 -0
- package/apis/dictType/ins.js +37 -0
- package/apis/dictType/list.js +26 -0
- package/apis/dictType/upd.js +51 -0
- package/apis/email/config.js +25 -0
- package/apis/email/logList.js +23 -0
- package/apis/email/send.js +66 -0
- package/apis/email/verify.js +21 -0
- package/apis/loginLog/list.js +23 -0
- package/apis/menu/all.js +41 -0
- package/apis/menu/list.js +25 -0
- package/apis/operateLog/list.js +23 -0
- package/apis/role/all.js +21 -0
- package/apis/role/apiSave.js +43 -0
- package/apis/role/apis.js +22 -0
- package/apis/role/del.js +49 -0
- package/apis/role/detail.js +32 -0
- package/apis/role/ins.js +46 -0
- package/apis/role/list.js +27 -0
- package/apis/role/menuSave.js +42 -0
- package/apis/role/menus.js +22 -0
- package/apis/role/save.js +40 -0
- package/apis/role/upd.js +50 -0
- package/apis/sysConfig/all.js +16 -0
- package/apis/sysConfig/del.js +36 -0
- package/apis/sysConfig/get.js +49 -0
- package/apis/sysConfig/ins.js +50 -0
- package/apis/sysConfig/list.js +24 -0
- package/apis/sysConfig/upd.js +62 -0
- package/checks/api.js +55 -0
- package/checks/config.js +107 -0
- package/checks/hook.js +38 -0
- package/checks/menu.js +58 -0
- package/checks/plugin.js +38 -0
- package/checks/table.js +78 -0
- package/configs/beflyConfig.json +61 -0
- package/configs/beflyMenus.json +85 -0
- package/configs/constConfig.js +34 -0
- package/configs/regexpAlias.json +55 -0
- package/hooks/auth.js +34 -0
- package/hooks/cors.js +39 -0
- package/hooks/parser.js +90 -0
- package/hooks/permission.js +71 -0
- package/hooks/validator.js +43 -0
- package/index.js +326 -0
- package/lib/cacheHelper.js +483 -0
- package/lib/cacheKeys.js +42 -0
- package/lib/connect.js +120 -0
- package/lib/dbHelper/builders.js +698 -0
- package/lib/dbHelper/context.js +131 -0
- package/lib/dbHelper/dataOps.js +505 -0
- package/lib/dbHelper/execute.js +65 -0
- package/lib/dbHelper/index.js +27 -0
- package/lib/dbHelper/transaction.js +43 -0
- package/lib/dbHelper/util.js +58 -0
- package/lib/dbHelper/validate.js +549 -0
- package/lib/emailHelper.js +110 -0
- package/lib/logger.js +604 -0
- package/lib/redisHelper.js +684 -0
- package/lib/sqlBuilder/batch.js +113 -0
- package/lib/sqlBuilder/check.js +150 -0
- package/lib/sqlBuilder/compiler.js +347 -0
- package/lib/sqlBuilder/errors.js +60 -0
- package/lib/sqlBuilder/index.js +218 -0
- package/lib/sqlBuilder/parser.js +296 -0
- package/lib/sqlBuilder/util.js +260 -0
- package/lib/validator.js +303 -0
- package/package.json +20 -12
- package/paths.js +112 -0
- package/plugins/cache.js +16 -0
- package/plugins/config.js +11 -0
- package/plugins/email.js +27 -0
- package/plugins/logger.js +20 -0
- package/plugins/mysql.js +36 -0
- package/plugins/redis.js +34 -0
- package/plugins/tool.js +155 -0
- package/router/api.js +140 -0
- package/router/static.js +71 -0
- package/scripts/syncDb/context.js +99 -0
- package/scripts/syncDb/diff.js +133 -0
- package/scripts/syncDb/index.js +70 -0
- package/scripts/syncDb/query.js +26 -0
- package/scripts/syncDb/report.js +190 -0
- package/scripts/syncDb/transform.js +111 -0
- package/sql/admin.sql +18 -0
- package/sql/api.sql +12 -0
- package/sql/dict.sql +13 -0
- package/sql/dictType.sql +12 -0
- package/sql/emailLog.sql +20 -0
- package/sql/loginLog.sql +25 -0
- package/sql/menu.sql +12 -0
- package/sql/operateLog.sql +22 -0
- package/sql/role.sql +14 -0
- package/sql/sysConfig.sql +16 -0
- package/sync/api.js +93 -0
- package/sync/cache.js +13 -0
- package/sync/dev.js +171 -0
- package/sync/menu.js +99 -0
- package/tables/admin.json +56 -0
- package/tables/api.json +26 -0
- package/tables/dict.json +30 -0
- package/tables/dictType.json +24 -0
- package/tables/emailLog.json +61 -0
- package/tables/loginLog.json +86 -0
- package/tables/menu.json +24 -0
- package/tables/operateLog.json +68 -0
- package/tables/role.json +32 -0
- package/tables/sysConfig.json +43 -0
- package/utils/calcPerfTime.js +13 -0
- package/utils/cors.js +17 -0
- package/utils/deepMerge.js +78 -0
- package/utils/fieldClear.js +65 -0
- package/utils/formatYmdHms.js +23 -0
- package/utils/formatZodIssues.js +109 -0
- package/utils/getClientIp.js +47 -0
- package/utils/importDefault.js +51 -0
- package/utils/is.js +462 -0
- package/utils/loggerUtils.js +185 -0
- package/utils/processInfo.js +39 -0
- package/utils/regexpUtil.js +52 -0
- package/utils/response.js +114 -0
- package/utils/scanFiles.js +124 -0
- package/utils/scanSources.js +68 -0
- package/utils/sortModules.js +75 -0
- package/utils/toSessionTtlSeconds.js +14 -0
- package/utils/util.js +374 -0
- package/befly.js +0 -12769
- package/befly.min.js +0 -47
|
@@ -0,0 +1,483 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 缓存助手 - JavaScript 版本
|
|
3
|
+
* 负责在服务器启动时缓存接口、菜单和角色权限到 Redis
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { CacheKeys } from "./cacheKeys.js";
|
|
7
|
+
import { Logger } from "./logger.js";
|
|
8
|
+
import { isNonEmptyString, isNullable, isString } from "../utils/is.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 缓存助手类
|
|
12
|
+
*/
|
|
13
|
+
export class CacheHelper {
|
|
14
|
+
constructor(deps) {
|
|
15
|
+
this.mysql = deps.mysql;
|
|
16
|
+
this.redis = deps.redis;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
assertApiPathList(value) {
|
|
20
|
+
if (isNullable(value)) return [];
|
|
21
|
+
|
|
22
|
+
let list = value;
|
|
23
|
+
|
|
24
|
+
if (isString(list)) {
|
|
25
|
+
const trimmed = list.trim();
|
|
26
|
+
if (trimmed === "" || trimmed === "null") {
|
|
27
|
+
return [];
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
|
|
31
|
+
try {
|
|
32
|
+
list = JSON.parse(trimmed);
|
|
33
|
+
} catch {
|
|
34
|
+
return [];
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
if (!Array.isArray(list)) {
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
const out = [];
|
|
44
|
+
for (const item of list) {
|
|
45
|
+
if (!isString(item)) continue;
|
|
46
|
+
const trimmed = item.trim();
|
|
47
|
+
if (!isNonEmptyString(trimmed)) continue;
|
|
48
|
+
out.push(trimmed);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return out;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
assertMenuPathList(value) {
|
|
55
|
+
if (isNullable(value)) return [];
|
|
56
|
+
|
|
57
|
+
let list = value;
|
|
58
|
+
|
|
59
|
+
if (isString(list)) {
|
|
60
|
+
const trimmed = list.trim();
|
|
61
|
+
if (trimmed === "" || trimmed === "null") {
|
|
62
|
+
return [];
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
if (trimmed.startsWith("[") && trimmed.endsWith("]")) {
|
|
66
|
+
try {
|
|
67
|
+
list = JSON.parse(trimmed);
|
|
68
|
+
} catch {
|
|
69
|
+
return [];
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (!Array.isArray(list)) {
|
|
75
|
+
return [];
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
const out = [];
|
|
79
|
+
for (const item of list) {
|
|
80
|
+
if (!isString(item)) continue;
|
|
81
|
+
const trimmed = item.trim();
|
|
82
|
+
if (!isNonEmptyString(trimmed)) continue;
|
|
83
|
+
out.push(trimmed);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
return out;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* 缓存所有接口到 Redis
|
|
91
|
+
*/
|
|
92
|
+
async cacheApis() {
|
|
93
|
+
try {
|
|
94
|
+
// 检查表是否存在
|
|
95
|
+
const tableExists = await this.mysql.tableExists("beflyApi");
|
|
96
|
+
if (!tableExists.data) {
|
|
97
|
+
Logger.warn("⚠️ 接口表不存在,跳过接口缓存");
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
// 从数据库查询所有接口
|
|
102
|
+
const apiList = await this.mysql.getAll({
|
|
103
|
+
table: "beflyApi"
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
// 缓存到 Redis
|
|
107
|
+
const result = await this.redis.setObject(CacheKeys.apisAll(), apiList.data.lists);
|
|
108
|
+
|
|
109
|
+
if (result === null) {
|
|
110
|
+
Logger.warn("⚠️ 接口缓存失败");
|
|
111
|
+
}
|
|
112
|
+
} catch (error) {
|
|
113
|
+
Logger.error("⚠️ 接口缓存异常", error);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* 缓存所有菜单到 Redis(从数据库读取)
|
|
119
|
+
*/
|
|
120
|
+
async cacheMenus() {
|
|
121
|
+
try {
|
|
122
|
+
// 检查表是否存在
|
|
123
|
+
const tableExists = await this.mysql.tableExists("beflyMenu");
|
|
124
|
+
if (!tableExists.data) {
|
|
125
|
+
Logger.warn("⚠️ 菜单表不存在,跳过菜单缓存");
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// 从数据库查询所有菜单
|
|
130
|
+
const menus = await this.mysql.getAll({
|
|
131
|
+
table: "beflyMenu"
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
// 缓存到 Redis
|
|
135
|
+
const result = await this.redis.setObject(CacheKeys.menusAll(), menus.data.lists);
|
|
136
|
+
|
|
137
|
+
if (result === null) {
|
|
138
|
+
Logger.warn("⚠️ 菜单缓存失败");
|
|
139
|
+
}
|
|
140
|
+
} catch (error) {
|
|
141
|
+
Logger.warn("⚠️ 菜单缓存异常", { err: error });
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* 缓存所有角色的接口权限到 Redis
|
|
147
|
+
* 全量重建:清理所有角色权限缓存并重建
|
|
148
|
+
* - 极简方案:每个角色一个 Set,直接覆盖更新(DEL + SADD)
|
|
149
|
+
*/
|
|
150
|
+
async cacheRoleApis() {
|
|
151
|
+
try {
|
|
152
|
+
// 检查表是否存在
|
|
153
|
+
const roleTableExists = await this.mysql.tableExists("beflyRole");
|
|
154
|
+
|
|
155
|
+
if (!roleTableExists.data) {
|
|
156
|
+
Logger.warn("⚠️ 角色表不存在,跳过角色权限缓存");
|
|
157
|
+
return;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// 查询所有角色(仅取必要字段)
|
|
161
|
+
const roles = await this.mysql.getAll({
|
|
162
|
+
table: "beflyRole",
|
|
163
|
+
fields: ["code", "apis"]
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
const roleApiPathsMap = new Map();
|
|
167
|
+
|
|
168
|
+
for (const role of roles.data.lists) {
|
|
169
|
+
if (!role?.code) continue;
|
|
170
|
+
const apiPaths = this.assertApiPathList(role.apis);
|
|
171
|
+
roleApiPathsMap.set(role.code, apiPaths);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
const roleCodes = Array.from(roleApiPathsMap.keys());
|
|
175
|
+
if (roleCodes.length === 0) {
|
|
176
|
+
Logger.info("✅ 没有需要缓存的角色权限");
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// 清理所有角色的缓存 key(保证幂等)
|
|
181
|
+
const roleKeys = roleCodes.map((code) => CacheKeys.roleApis(code));
|
|
182
|
+
await this.redis.delBatch(roleKeys);
|
|
183
|
+
|
|
184
|
+
// 批量写入新缓存(只写入非空权限)
|
|
185
|
+
const items = [];
|
|
186
|
+
|
|
187
|
+
for (const roleCode of roleCodes) {
|
|
188
|
+
const apiPaths = roleApiPathsMap.get(roleCode) || [];
|
|
189
|
+
const members = Array.from(new Set(apiPaths)).sort();
|
|
190
|
+
|
|
191
|
+
if (members.length > 0) {
|
|
192
|
+
items.push({ key: CacheKeys.roleApis(roleCode), members: members });
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (items.length > 0) {
|
|
197
|
+
await this.redis.saddBatch(items);
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// 极简方案不做版本/ready/meta:重建完成即生效
|
|
201
|
+
} catch (error) {
|
|
202
|
+
Logger.error("⚠️ 角色权限缓存异常(将阻断启动)", error);
|
|
203
|
+
throw new Error("⚠️ 角色权限缓存异常(将阻断启动)", {
|
|
204
|
+
cause: error,
|
|
205
|
+
code: "runtime",
|
|
206
|
+
subsystem: "cache",
|
|
207
|
+
operation: "cacheRoleApis"
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* 缓存所有角色的菜单权限到 Redis
|
|
214
|
+
* 全量重建:清理所有角色菜单权限缓存并重建
|
|
215
|
+
* - 每个角色一个 Set,直接覆盖更新(DEL + SADD)
|
|
216
|
+
*/
|
|
217
|
+
async cacheRoleMenus() {
|
|
218
|
+
try {
|
|
219
|
+
// 检查表是否存在
|
|
220
|
+
const roleTableExists = await this.mysql.tableExists("beflyRole");
|
|
221
|
+
|
|
222
|
+
if (!roleTableExists.data) {
|
|
223
|
+
Logger.warn("⚠️ 角色表不存在,跳过角色菜单权限缓存");
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// 查询所有角色(仅取必要字段)
|
|
228
|
+
const roles = await this.mysql.getAll({
|
|
229
|
+
table: "beflyRole",
|
|
230
|
+
fields: ["code", "menus"]
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
const roleMenuPathsMap = new Map();
|
|
234
|
+
|
|
235
|
+
for (const role of roles.data.lists) {
|
|
236
|
+
if (!role?.code) continue;
|
|
237
|
+
const menuPaths = this.assertMenuPathList(role.menus);
|
|
238
|
+
roleMenuPathsMap.set(role.code, menuPaths);
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
const roleCodes = Array.from(roleMenuPathsMap.keys());
|
|
242
|
+
if (roleCodes.length === 0) {
|
|
243
|
+
Logger.info("✅ 没有需要缓存的角色菜单权限");
|
|
244
|
+
return;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// 清理所有角色的缓存 key(保证幂等)
|
|
248
|
+
const roleKeys = roleCodes.map((code) => CacheKeys.roleMenus(code));
|
|
249
|
+
await this.redis.delBatch(roleKeys);
|
|
250
|
+
|
|
251
|
+
// 批量写入新缓存(只写入非空权限)
|
|
252
|
+
const items = [];
|
|
253
|
+
|
|
254
|
+
for (const roleCode of roleCodes) {
|
|
255
|
+
const menuPaths = roleMenuPathsMap.get(roleCode) || [];
|
|
256
|
+
const members = Array.from(new Set(menuPaths)).sort();
|
|
257
|
+
|
|
258
|
+
if (members.length > 0) {
|
|
259
|
+
items.push({ key: CacheKeys.roleMenus(roleCode), members: members });
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (items.length > 0) {
|
|
264
|
+
await this.redis.saddBatch(items);
|
|
265
|
+
}
|
|
266
|
+
} catch (error) {
|
|
267
|
+
Logger.error("⚠️ 角色菜单权限缓存异常(将阻断启动)", error);
|
|
268
|
+
throw new Error("⚠️ 角色菜单权限缓存异常(将阻断启动)", {
|
|
269
|
+
cause: error,
|
|
270
|
+
code: "runtime",
|
|
271
|
+
subsystem: "cache",
|
|
272
|
+
operation: "cacheRoleMenus"
|
|
273
|
+
});
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* 增量刷新单个角色的接口权限缓存
|
|
279
|
+
* - apiIds 为空数组:仅清理缓存(防止残留)
|
|
280
|
+
* - apiIds 非空:使用 $in 最小查询,DEL 后 SADD
|
|
281
|
+
*/
|
|
282
|
+
async refreshRoleApiPermissions(roleCode, apiPaths) {
|
|
283
|
+
if (!isNonEmptyString(roleCode)) {
|
|
284
|
+
return;
|
|
285
|
+
}
|
|
286
|
+
if (!Array.isArray(apiPaths)) {
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const normalizedPaths = this.assertApiPathList(apiPaths);
|
|
291
|
+
const roleKey = CacheKeys.roleApis(roleCode);
|
|
292
|
+
|
|
293
|
+
// 空数组短路:保证清理残留
|
|
294
|
+
if (normalizedPaths.length === 0) {
|
|
295
|
+
await this.redis.del(roleKey);
|
|
296
|
+
return;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
const members = Array.from(new Set(normalizedPaths));
|
|
300
|
+
|
|
301
|
+
await this.redis.del(roleKey);
|
|
302
|
+
if (members.length > 0) {
|
|
303
|
+
await this.redis.sadd(roleKey, members);
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
/**
|
|
308
|
+
* 增量刷新单个角色的菜单权限缓存
|
|
309
|
+
* - menuPaths 为空数组:仅清理缓存(防止残留)
|
|
310
|
+
* - menuPaths 非空:DEL 后 SADD
|
|
311
|
+
*/
|
|
312
|
+
async refreshRoleMenuPermissions(roleCode, menuPaths) {
|
|
313
|
+
if (!isNonEmptyString(roleCode)) {
|
|
314
|
+
return;
|
|
315
|
+
}
|
|
316
|
+
if (!Array.isArray(menuPaths)) {
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
const normalizedPaths = this.assertMenuPathList(menuPaths);
|
|
321
|
+
const roleKey = CacheKeys.roleMenus(roleCode);
|
|
322
|
+
|
|
323
|
+
// 空数组短路:保证清理残留
|
|
324
|
+
if (normalizedPaths.length === 0) {
|
|
325
|
+
await this.redis.del(roleKey);
|
|
326
|
+
return;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
const members = Array.from(new Set(normalizedPaths));
|
|
330
|
+
|
|
331
|
+
await this.redis.del(roleKey);
|
|
332
|
+
if (members.length > 0) {
|
|
333
|
+
await this.redis.sadd(roleKey, members);
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
/**
|
|
338
|
+
* 缓存所有数据
|
|
339
|
+
*/
|
|
340
|
+
async cacheAll() {
|
|
341
|
+
// 1. 缓存接口
|
|
342
|
+
await this.cacheApis();
|
|
343
|
+
|
|
344
|
+
// 2. 缓存菜单
|
|
345
|
+
await this.cacheMenus();
|
|
346
|
+
|
|
347
|
+
// 3. 缓存角色权限
|
|
348
|
+
await this.cacheRoleApis();
|
|
349
|
+
|
|
350
|
+
// 4. 缓存角色菜单权限
|
|
351
|
+
await this.cacheRoleMenus();
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
/**
|
|
355
|
+
* 获取缓存的所有接口
|
|
356
|
+
* @returns 接口列表
|
|
357
|
+
*/
|
|
358
|
+
async getApis() {
|
|
359
|
+
try {
|
|
360
|
+
const apis = await this.redis.getObject(CacheKeys.apisAll());
|
|
361
|
+
return apis || [];
|
|
362
|
+
} catch (error) {
|
|
363
|
+
Logger.error("获取接口缓存失败", error);
|
|
364
|
+
return [];
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
/**
|
|
369
|
+
* 获取缓存的所有菜单
|
|
370
|
+
* @returns 菜单列表
|
|
371
|
+
*/
|
|
372
|
+
async getMenus() {
|
|
373
|
+
try {
|
|
374
|
+
const menus = await this.redis.getObject(CacheKeys.menusAll());
|
|
375
|
+
return menus || [];
|
|
376
|
+
} catch (error) {
|
|
377
|
+
Logger.error("获取菜单缓存失败", error);
|
|
378
|
+
return [];
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
/**
|
|
383
|
+
* 获取角色的接口权限
|
|
384
|
+
* @param roleCode - 角色代码
|
|
385
|
+
* @returns 接口路径列表
|
|
386
|
+
*/
|
|
387
|
+
async getRolePermissions(roleCode) {
|
|
388
|
+
try {
|
|
389
|
+
const permissions = await this.redis.smembers(CacheKeys.roleApis(roleCode));
|
|
390
|
+
return permissions || [];
|
|
391
|
+
} catch (error) {
|
|
392
|
+
Logger.error("获取角色权限缓存失败", error, { roleCode: roleCode });
|
|
393
|
+
return [];
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
/**
|
|
398
|
+
* 获取角色的菜单权限
|
|
399
|
+
* @param roleCode - 角色代码
|
|
400
|
+
* @returns 菜单路径列表(menu.path)
|
|
401
|
+
*/
|
|
402
|
+
async getRoleMenuPermissions(roleCode) {
|
|
403
|
+
try {
|
|
404
|
+
const permissions = await this.redis.smembers(CacheKeys.roleMenus(roleCode));
|
|
405
|
+
return permissions || [];
|
|
406
|
+
} catch (error) {
|
|
407
|
+
Logger.error("获取角色菜单权限缓存失败", error, { roleCode: roleCode });
|
|
408
|
+
return [];
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/**
|
|
413
|
+
* 检查角色是否有指定接口权限
|
|
414
|
+
* @param roleCode - 角色代码
|
|
415
|
+
* @param apiPath - 接口路径(url.pathname,例如 /api/user/login;与 method 无关)
|
|
416
|
+
* @returns 是否有权限
|
|
417
|
+
*/
|
|
418
|
+
async checkRolePermission(roleCode, apiPath) {
|
|
419
|
+
try {
|
|
420
|
+
if (isNullable(apiPath)) return false;
|
|
421
|
+
const value = isString(apiPath) ? apiPath : String(apiPath);
|
|
422
|
+
return await this.redis.sismember(CacheKeys.roleApis(roleCode), value);
|
|
423
|
+
} catch (error) {
|
|
424
|
+
Logger.error("检查角色权限失败", error, { roleCode: roleCode });
|
|
425
|
+
return false;
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* 检查角色是否有指定菜单权限
|
|
431
|
+
* @param roleCode - 角色代码
|
|
432
|
+
* @param menuPath - 菜单路径(menu.path)
|
|
433
|
+
* @returns 是否有权限
|
|
434
|
+
*/
|
|
435
|
+
async checkRoleMenuPermission(roleCode, menuPath) {
|
|
436
|
+
try {
|
|
437
|
+
if (isNullable(menuPath)) return false;
|
|
438
|
+
const value = isString(menuPath) ? menuPath : String(menuPath);
|
|
439
|
+
return await this.redis.sismember(CacheKeys.roleMenus(roleCode), value);
|
|
440
|
+
} catch (error) {
|
|
441
|
+
Logger.error("检查角色菜单权限失败", error, { roleCode: roleCode });
|
|
442
|
+
return false;
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
/**
|
|
447
|
+
* 删除角色的接口权限缓存
|
|
448
|
+
* @param roleCode - 角色代码
|
|
449
|
+
* @returns 是否删除成功
|
|
450
|
+
*/
|
|
451
|
+
async deleteRolePermissions(roleCode) {
|
|
452
|
+
try {
|
|
453
|
+
const result = await this.redis.del(CacheKeys.roleApis(roleCode));
|
|
454
|
+
if (result > 0) {
|
|
455
|
+
Logger.info(`✅ 已删除角色 ${roleCode} 的权限缓存`);
|
|
456
|
+
return true;
|
|
457
|
+
}
|
|
458
|
+
return false;
|
|
459
|
+
} catch (error) {
|
|
460
|
+
Logger.error("删除角色权限缓存失败", error, { roleCode: roleCode });
|
|
461
|
+
return false;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
/**
|
|
466
|
+
* 删除角色的菜单权限缓存
|
|
467
|
+
* @param roleCode - 角色代码
|
|
468
|
+
* @returns 是否删除成功
|
|
469
|
+
*/
|
|
470
|
+
async deleteRoleMenuPermissions(roleCode) {
|
|
471
|
+
try {
|
|
472
|
+
const result = await this.redis.del(CacheKeys.roleMenus(roleCode));
|
|
473
|
+
if (result > 0) {
|
|
474
|
+
Logger.info(`✅ 已删除角色 ${roleCode} 的菜单权限缓存`);
|
|
475
|
+
return true;
|
|
476
|
+
}
|
|
477
|
+
return false;
|
|
478
|
+
} catch (error) {
|
|
479
|
+
Logger.error("删除角色菜单权限缓存失败", error, { roleCode: roleCode });
|
|
480
|
+
return false;
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
}
|
package/lib/cacheKeys.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cache Key 统一管理
|
|
3
|
+
* 所有缓存键在此统一定义,避免硬编码分散
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Cache Key 生成函数集合
|
|
8
|
+
*/
|
|
9
|
+
export class CacheKeys {
|
|
10
|
+
/** 所有接口缓存2 */
|
|
11
|
+
static apisAll() {
|
|
12
|
+
return "apis:all";
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** 所有菜单缓存 */
|
|
16
|
+
static menusAll() {
|
|
17
|
+
return "menus:all";
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** 角色信息缓存(完整角色对象) */
|
|
21
|
+
static roleInfo(roleCode) {
|
|
22
|
+
return `role:info:${roleCode}`;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 角色菜单权限缓存(Set 集合)
|
|
27
|
+
* - key: role:menus:${roleCode}
|
|
28
|
+
* - member: menu.path(例如 /permission/role)
|
|
29
|
+
*/
|
|
30
|
+
static roleMenus(roleCode) {
|
|
31
|
+
return `role:menus:${roleCode}`;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 角色接口权限缓存(Set 集合)
|
|
36
|
+
* - key: role:apis:${roleCode}
|
|
37
|
+
* - member: url.pathname(例如 /api/user/login;与 method 无关)
|
|
38
|
+
*/
|
|
39
|
+
static roleApis(roleCode) {
|
|
40
|
+
return `role:apis:${roleCode}`;
|
|
41
|
+
}
|
|
42
|
+
}
|
package/lib/connect.js
ADDED
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 连接管理器 - JavaScript 版本
|
|
3
|
+
* 负责管理数据库和 Redis 连接
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { RedisClient, SQL } from "bun";
|
|
7
|
+
import { Logger } from "./logger.js";
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* 连接管理器
|
|
11
|
+
*/
|
|
12
|
+
export class Connect {
|
|
13
|
+
static mysqlClient = null;
|
|
14
|
+
static redisClient = null;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* 获取数据库客户端
|
|
18
|
+
* @returns 数据库客户端
|
|
19
|
+
*/
|
|
20
|
+
static getMysql() {
|
|
21
|
+
return this.mysqlClient;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* 获取 Redis 客户端
|
|
26
|
+
* @returns Redis 客户端
|
|
27
|
+
*/
|
|
28
|
+
static getRedis() {
|
|
29
|
+
return this.redisClient;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* 连接数据库
|
|
34
|
+
* @param config - 数据库配置
|
|
35
|
+
*/
|
|
36
|
+
static async connectMysql(config) {
|
|
37
|
+
try {
|
|
38
|
+
const sqlConfig = {
|
|
39
|
+
adapter: "mysql",
|
|
40
|
+
hostname: config.hostname,
|
|
41
|
+
port: config.port,
|
|
42
|
+
database: config.database,
|
|
43
|
+
username: config.username,
|
|
44
|
+
password: config.password,
|
|
45
|
+
max: config.max
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
this.mysqlClient = new SQL(sqlConfig);
|
|
49
|
+
|
|
50
|
+
await this.mysqlClient`SELECT 1`;
|
|
51
|
+
Logger.info("Mysql 连接成功");
|
|
52
|
+
} catch (error) {
|
|
53
|
+
await this.mysqlClient?.close();
|
|
54
|
+
this.mysqlClient = null;
|
|
55
|
+
Logger.error("Mysql 连接失败", error);
|
|
56
|
+
throw new Error("Mysql 连接失败", {
|
|
57
|
+
cause: error,
|
|
58
|
+
code: "runtime",
|
|
59
|
+
subsystem: "mysql",
|
|
60
|
+
operation: "connect"
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* 连接 Redis
|
|
67
|
+
* @param config - Redis 配置
|
|
68
|
+
*/
|
|
69
|
+
static async connectRedis(config) {
|
|
70
|
+
try {
|
|
71
|
+
let authPart = "";
|
|
72
|
+
if (config.username) {
|
|
73
|
+
const encodedUser = encodeURIComponent(config.username);
|
|
74
|
+
const encodedPass = encodeURIComponent(config.password || "");
|
|
75
|
+
authPart = `${encodedUser}:${encodedPass}@`;
|
|
76
|
+
} else if (config.password) {
|
|
77
|
+
const encodedPass = encodeURIComponent(config.password);
|
|
78
|
+
authPart = `:${encodedPass}@`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const redisUrl = `redis://${authPart}${config.hostname}:${config.port}/${config.db}`;
|
|
82
|
+
|
|
83
|
+
this.redisClient = new RedisClient(redisUrl);
|
|
84
|
+
// Called when successfully connected to Redis server
|
|
85
|
+
this.redisClient.onconnect = () => {
|
|
86
|
+
Logger.info("Redis 连接成功");
|
|
87
|
+
};
|
|
88
|
+
|
|
89
|
+
// Called when disconnected from Redis server
|
|
90
|
+
this.redisClient.onclose = (error) => {
|
|
91
|
+
Logger.warn("Redis 断开连接", {
|
|
92
|
+
subsystem: "redis",
|
|
93
|
+
operation: "onclose",
|
|
94
|
+
cause: error
|
|
95
|
+
});
|
|
96
|
+
};
|
|
97
|
+
} catch (error) {
|
|
98
|
+
await this.redisClient?.close();
|
|
99
|
+
this.redisClient = null;
|
|
100
|
+
Logger.error("Redis 连接失败", error);
|
|
101
|
+
throw new Error("Redis 连接失败", {
|
|
102
|
+
cause: error,
|
|
103
|
+
code: "runtime",
|
|
104
|
+
subsystem: "redis",
|
|
105
|
+
operation: "connect"
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* 断开连接
|
|
112
|
+
*/
|
|
113
|
+
static async disconnect() {
|
|
114
|
+
await this.mysqlClient?.close();
|
|
115
|
+
this.mysqlClient = null;
|
|
116
|
+
|
|
117
|
+
await this.redisClient?.close();
|
|
118
|
+
this.redisClient = null;
|
|
119
|
+
}
|
|
120
|
+
}
|