befly 3.9.40 → 3.10.0
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 +39 -8
- package/befly.config.ts +19 -2
- package/checks/checkApi.ts +79 -77
- package/checks/checkHook.ts +48 -0
- package/checks/checkMenu.ts +168 -0
- package/checks/checkPlugin.ts +48 -0
- package/checks/checkTable.ts +137 -183
- package/docs/README.md +1 -1
- package/docs/api/api.md +1 -1
- package/docs/guide/quickstart.md +16 -9
- package/docs/hooks/hook.md +2 -2
- package/docs/hooks/rateLimit.md +1 -1
- package/docs/infra/redis.md +7 -7
- package/docs/plugins/plugin.md +23 -21
- package/docs/quickstart.md +16 -9
- package/docs/reference/addon.md +12 -1
- package/docs/reference/config.md +13 -30
- package/docs/reference/sync.md +62 -193
- package/docs/reference/table.md +27 -29
- package/hooks/auth.ts +3 -4
- package/hooks/cors.ts +4 -6
- package/hooks/parser.ts +3 -4
- package/hooks/permission.ts +3 -4
- package/hooks/validator.ts +3 -4
- package/lib/cacheHelper.ts +89 -153
- package/lib/cacheKeys.ts +1 -1
- package/lib/connect.ts +9 -13
- package/lib/dbDialect.ts +285 -0
- package/lib/dbHelper.ts +179 -507
- package/lib/dbUtils.ts +450 -0
- package/lib/logger.ts +41 -5
- package/lib/redisHelper.ts +1 -0
- package/lib/sqlBuilder.ts +358 -58
- package/lib/sqlCheck.ts +136 -0
- package/lib/validator.ts +1 -1
- package/loader/loadApis.ts +23 -126
- package/loader/loadHooks.ts +31 -46
- package/loader/loadPlugins.ts +37 -52
- package/main.ts +58 -19
- package/package.json +24 -25
- package/paths.ts +14 -14
- package/plugins/cache.ts +12 -6
- package/plugins/cipher.ts +2 -2
- package/plugins/config.ts +6 -8
- package/plugins/db.ts +14 -19
- package/plugins/jwt.ts +6 -7
- package/plugins/logger.ts +7 -9
- package/plugins/redis.ts +8 -10
- package/plugins/tool.ts +3 -4
- package/router/api.ts +3 -2
- package/router/static.ts +7 -5
- package/sync/syncApi.ts +80 -235
- package/sync/syncCache.ts +16 -0
- package/sync/syncDev.ts +167 -202
- package/sync/syncMenu.ts +230 -444
- package/sync/syncTable.ts +1247 -0
- package/tests/_mocks/mockSqliteDb.ts +204 -0
- package/tests/addonHelper-cache.test.ts +32 -0
- package/tests/apiHandler-routePath-only.test.ts +32 -0
- package/tests/cacheHelper.test.ts +16 -51
- package/tests/checkApi-routePath-strict.test.ts +166 -0
- package/tests/checkMenu.test.ts +346 -0
- package/tests/checkTable-smoke.test.ts +157 -0
- package/tests/dbDialect-cache.test.ts +23 -0
- package/tests/dbDialect.test.ts +46 -0
- package/tests/dbHelper-advanced.test.ts +1 -1
- package/tests/dbHelper-all-array-types.test.ts +15 -15
- package/tests/dbHelper-batch-write.test.ts +90 -0
- package/tests/dbHelper-columns.test.ts +36 -54
- package/tests/dbHelper-execute.test.ts +26 -26
- package/tests/dbHelper-joins.test.ts +85 -176
- package/tests/fixtures/scanFilesAddon/node_modules/@befly-addon/demo/apis/sub/b.ts +3 -0
- package/tests/fixtures/scanFilesApis/a.ts +3 -0
- package/tests/fixtures/scanFilesApis/sub/b.ts +3 -0
- package/tests/loadPlugins-order-smoke.test.ts +75 -0
- package/tests/logger.test.ts +6 -6
- package/tests/redisHelper.test.ts +6 -1
- package/tests/scanFiles-routePath.test.ts +46 -0
- package/tests/smoke-sql.test.ts +24 -0
- package/tests/sqlBuilder-advanced.test.ts +18 -5
- package/tests/sqlBuilder.test.ts +24 -0
- package/tests/sync-init-guard.test.ts +105 -0
- package/tests/syncApi-insBatch-fields-consistent.test.ts +61 -0
- package/tests/syncApi-obsolete-records.test.ts +69 -0
- package/tests/syncApi-type-compat.test.ts +72 -0
- package/tests/syncDev-permissions.test.ts +81 -0
- package/tests/syncMenu-disableMenus-hard-delete.test.ts +88 -0
- package/tests/syncMenu-duplicate-path.test.ts +122 -0
- package/tests/syncMenu-obsolete-records.test.ts +161 -0
- package/tests/syncMenu-parentPath-from-tree.test.ts +75 -0
- package/tests/syncMenu-paths.test.ts +0 -9
- package/tests/{syncDb-apply.test.ts → syncTable-apply.test.ts} +14 -24
- package/tests/{syncDb-array-number.test.ts → syncTable-array-number.test.ts} +31 -31
- package/tests/syncTable-constants.test.ts +101 -0
- package/tests/syncTable-db-integration.test.ts +237 -0
- package/tests/{syncDb-ddl.test.ts → syncTable-ddl.test.ts} +67 -53
- package/tests/{syncDb-helpers.test.ts → syncTable-helpers.test.ts} +12 -26
- package/tests/syncTable-schema.test.ts +99 -0
- package/tests/syncTable-testkit.test.ts +25 -0
- package/tests/syncTable-types.test.ts +122 -0
- package/tests/tableRef-and-deserialize.test.ts +67 -0
- package/tsconfig.json +1 -1
- package/types/api.d.ts +1 -1
- package/types/befly.d.ts +13 -12
- package/types/cache.d.ts +2 -2
- package/types/context.d.ts +1 -1
- package/types/database.d.ts +0 -5
- package/types/hook.d.ts +1 -10
- package/types/plugin.d.ts +2 -96
- package/types/sync.d.ts +19 -25
- package/utils/convertBigIntFields.ts +38 -0
- package/utils/disableMenusGlob.ts +85 -0
- package/utils/importDefault.ts +21 -0
- package/utils/isDirentDirectory.ts +23 -0
- package/utils/loadMenuConfigs.ts +145 -0
- package/utils/processFields.ts +25 -0
- package/utils/scanAddons.ts +72 -0
- package/utils/scanFiles.ts +129 -21
- package/utils/scanSources.ts +64 -0
- package/utils/sortModules.ts +137 -0
- package/checks/checkApp.ts +0 -55
- package/hooks/rateLimit.ts +0 -276
- package/sync/syncAll.ts +0 -35
- package/sync/syncDb/apply.ts +0 -192
- package/sync/syncDb/constants.ts +0 -119
- package/sync/syncDb/ddl.ts +0 -251
- package/sync/syncDb/helpers.ts +0 -84
- package/sync/syncDb/schema.ts +0 -202
- package/sync/syncDb/sqlite.ts +0 -48
- package/sync/syncDb/table.ts +0 -207
- package/sync/syncDb/tableCreate.ts +0 -163
- package/sync/syncDb/types.ts +0 -132
- package/sync/syncDb/version.ts +0 -69
- package/sync/syncDb.ts +0 -168
- package/tests/rateLimit-hook.test.ts +0 -477
- package/tests/syncDb-constants.test.ts +0 -130
- package/tests/syncDb-schema.test.ts +0 -179
- package/tests/syncDb-types.test.ts +0 -139
- package/utils/addonHelper.ts +0 -90
- package/utils/modules.ts +0 -98
- package/utils/route.ts +0 -23
package/sync/syncApi.ts
CHANGED
|
@@ -1,269 +1,114 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
* 说明:遍历所有 API 文件,收集接口路由信息并同步到 addon_admin_api 表
|
|
4
|
-
*
|
|
5
|
-
* 流程:
|
|
6
|
-
* 1. 扫描项目 apis 目录下所有项目 API 文件
|
|
7
|
-
* 2. 扫描 node_modules/@befly-addon/* 目录下所有组件 API 文件
|
|
8
|
-
* 3. 提取每个 API 的 name、method、auth 等信息
|
|
9
|
-
* 4. 根据接口路径检查是否存在
|
|
10
|
-
* 5. 存在则更新,不存在则新增
|
|
11
|
-
* 6. 删除配置中不存在的接口记录
|
|
12
|
-
*/
|
|
13
|
-
import type { SyncApiOptions, ApiInfo } from "../types/sync.js";
|
|
1
|
+
import type { BeflyContext } from "../types/befly.js";
|
|
2
|
+
import type { SyncApiItem } from "../types/sync.js";
|
|
14
3
|
|
|
15
|
-
import {
|
|
4
|
+
import { keyBy } from "es-toolkit/array";
|
|
16
5
|
|
|
17
|
-
import { CacheHelper } from "../lib/cacheHelper.js";
|
|
18
|
-
import { Connect } from "../lib/connect.js";
|
|
19
|
-
import { DbHelper } from "../lib/dbHelper.js";
|
|
20
6
|
import { Logger } from "../lib/logger.js";
|
|
21
|
-
import { RedisHelper } from "../lib/redisHelper.js";
|
|
22
|
-
import { projectDir } from "../paths.js";
|
|
23
|
-
import { scanAddons, addonDirExists, getAddonDir } from "../utils/addonHelper.js";
|
|
24
|
-
import { scanFiles } from "../utils/scanFiles.js";
|
|
25
7
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
*/
|
|
29
|
-
async function extractApiInfo(filePath: string, apiRoot: string, type: "app" | "addon", addonName: string = "", addonTitle: string = ""): Promise<ApiInfo | null> {
|
|
30
|
-
try {
|
|
31
|
-
const normalizedFilePath = filePath.replace(/\\/g, "/");
|
|
32
|
-
const apiModule = await import(normalizedFilePath);
|
|
33
|
-
const apiConfig = apiModule.default;
|
|
8
|
+
export async function syncApi(ctx: Pick<BeflyContext, "db" | "cache">, apis: SyncApiItem[]): Promise<void> {
|
|
9
|
+
const tableName = "addon_admin_api";
|
|
34
10
|
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
let apiPath = "";
|
|
40
|
-
|
|
41
|
-
if (type === "addon") {
|
|
42
|
-
// Addon 接口:保留完整目录层级
|
|
43
|
-
// 例: apis/menu/list.ts → /api/addon/admin/menu/list
|
|
44
|
-
const relativePath = relative(apiRoot, filePath);
|
|
45
|
-
const pathWithoutExt = relativePath.replace(/\.(ts|js)$/, "");
|
|
46
|
-
apiPath = `/api/addon/${addonName}/${pathWithoutExt}`;
|
|
47
|
-
} else {
|
|
48
|
-
// 项目接口:保留完整目录层级
|
|
49
|
-
// 例: apis/user/list.ts → /api/user/list
|
|
50
|
-
const relativePath = relative(apiRoot, filePath);
|
|
51
|
-
const pathWithoutExt = relativePath.replace(/\.(ts|js)$/, "");
|
|
52
|
-
apiPath = `/api/${pathWithoutExt}`;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
return {
|
|
56
|
-
name: apiConfig.name || "",
|
|
57
|
-
path: apiPath,
|
|
58
|
-
method: apiConfig.method || "POST",
|
|
59
|
-
description: apiConfig.description || "",
|
|
60
|
-
addonName: addonName,
|
|
61
|
-
addonTitle: addonTitle || addonName
|
|
62
|
-
};
|
|
63
|
-
} catch (error: any) {
|
|
64
|
-
Logger.error({ err: error }, "同步 API 失败");
|
|
65
|
-
throw error;
|
|
11
|
+
if (!ctx.db) {
|
|
12
|
+
throw new Error("syncApi: ctx.db 未初始化(Db 插件未加载或注入失败)");
|
|
66
13
|
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* 扫描所有 API 文件
|
|
71
|
-
*/
|
|
72
|
-
async function scanAllApis(): Promise<ApiInfo[]> {
|
|
73
|
-
const apis: ApiInfo[] = [];
|
|
74
|
-
|
|
75
|
-
// 1. 扫描项目 API(只扫描 apis 目录)
|
|
76
|
-
try {
|
|
77
|
-
const projectApisDir = join(projectDir, "apis");
|
|
78
|
-
|
|
79
|
-
// 扫描项目 API 文件
|
|
80
|
-
const projectApiFiles: string[] = [];
|
|
81
|
-
try {
|
|
82
|
-
const files = await scanFiles(projectApisDir);
|
|
83
|
-
for (const { filePath } of files) {
|
|
84
|
-
projectApiFiles.push(filePath);
|
|
85
|
-
}
|
|
86
|
-
} catch (error: any) {
|
|
87
|
-
Logger.warn(`扫描项目 API 目录失败: ${projectApisDir} - ${error.message}`);
|
|
88
|
-
}
|
|
89
14
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
apis.push(apiInfo);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
15
|
+
if (!ctx.cache) {
|
|
16
|
+
throw new Error("syncApi: ctx.cache 未初始化(cache 插件未加载或注入失败)");
|
|
17
|
+
}
|
|
96
18
|
|
|
97
|
-
|
|
98
|
-
|
|
19
|
+
if (!(await ctx.db.tableExists(tableName))) {
|
|
20
|
+
Logger.debug(`${tableName} 表不存在`);
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
99
23
|
|
|
100
|
-
|
|
101
|
-
|
|
24
|
+
const allDbApis = await ctx.db.getAll({
|
|
25
|
+
table: tableName,
|
|
26
|
+
fields: ["id", "routePath", "name", "addonName", "state"],
|
|
27
|
+
where: { state$gte: 0 }
|
|
28
|
+
} as any);
|
|
102
29
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
continue;
|
|
106
|
-
}
|
|
30
|
+
const dbLists = allDbApis.lists || [];
|
|
31
|
+
const allDbApiMap = keyBy(dbLists, (item: any) => item.routePath);
|
|
107
32
|
|
|
108
|
-
|
|
33
|
+
const insData: SyncApiItem[] = [];
|
|
34
|
+
const updData: Array<{ id: number; api: SyncApiItem }> = [];
|
|
35
|
+
const delData: number[] = [];
|
|
109
36
|
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
let addonTitle = addonName;
|
|
113
|
-
try {
|
|
114
|
-
const packageJson = await import(addonPackageJsonPath, { with: { type: "json" } });
|
|
115
|
-
addonTitle = packageJson.default?.title || addonName;
|
|
116
|
-
} catch {
|
|
117
|
-
// 忽略配置读取错误
|
|
118
|
-
}
|
|
37
|
+
// 1) 先构建当前扫描到的 routePath 集合(用于删除差集)
|
|
38
|
+
const apiRouteKeys = new Set<string>();
|
|
119
39
|
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
Logger.warn(`扫描 addon API 目录失败: ${addonApisDir} - ${error.message}`);
|
|
129
|
-
}
|
|
40
|
+
// 2) 插入 / 更新(存在不一定更新:仅当 name/routePath/addonName 任一不匹配时更新)
|
|
41
|
+
for (const api of apis) {
|
|
42
|
+
const apiType = api.type;
|
|
43
|
+
// 兼容:历史/测试构造的数据可能没有 type 字段;此时应按 API 处理。
|
|
44
|
+
// 因此仅当 type **显式存在** 且不为 "api" 时才跳过,避免误把真实 API 条目过滤掉。
|
|
45
|
+
if (apiType && apiType !== "api") {
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
130
48
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
49
|
+
const routePath = api.routePath;
|
|
50
|
+
apiRouteKeys.add(api.routePath);
|
|
51
|
+
const item = (allDbApiMap as any)[routePath];
|
|
52
|
+
if (item) {
|
|
53
|
+
const shouldUpdate = api.name !== item.name || api.routePath !== item.routePath || api.addonName !== item.addonName;
|
|
54
|
+
if (shouldUpdate) {
|
|
55
|
+
updData.push({ id: item.id, api: api });
|
|
136
56
|
}
|
|
57
|
+
} else {
|
|
58
|
+
insData.push(api);
|
|
137
59
|
}
|
|
60
|
+
}
|
|
138
61
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
62
|
+
// 3) 删除:用差集(DB - 当前扫描)得到要删除的 id
|
|
63
|
+
for (const record of dbLists) {
|
|
64
|
+
if (!apiRouteKeys.has(record.routePath)) {
|
|
65
|
+
delData.push(record.id);
|
|
66
|
+
}
|
|
143
67
|
}
|
|
144
|
-
}
|
|
145
68
|
|
|
146
|
-
|
|
147
|
-
* 同步 API 数据到数据库
|
|
148
|
-
*/
|
|
149
|
-
async function syncApis(helper: any, apis: ApiInfo[]): Promise<void> {
|
|
150
|
-
for (const api of apis) {
|
|
69
|
+
if (updData.length > 0) {
|
|
151
70
|
try {
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
if (existing) {
|
|
159
|
-
// 检查是否需要更新
|
|
160
|
-
const needUpdate = existing.name !== api.name || existing.method !== api.method || existing.description !== api.description || existing.addonName !== api.addonName || existing.addonTitle !== api.addonTitle;
|
|
161
|
-
|
|
162
|
-
if (needUpdate) {
|
|
163
|
-
await helper.updData({
|
|
164
|
-
table: "addon_admin_api",
|
|
165
|
-
where: { id: existing.id },
|
|
71
|
+
await ctx.db.updBatch(
|
|
72
|
+
tableName,
|
|
73
|
+
updData.map((item) => {
|
|
74
|
+
return {
|
|
75
|
+
id: item.id,
|
|
166
76
|
data: {
|
|
167
|
-
name: api.name,
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
addonName: api.addonName,
|
|
171
|
-
addonTitle: api.addonTitle
|
|
77
|
+
name: item.api.name,
|
|
78
|
+
routePath: item.api.routePath,
|
|
79
|
+
addonName: item.api.addonName
|
|
172
80
|
}
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
await helper.insData({
|
|
177
|
-
table: "addon_admin_api",
|
|
178
|
-
data: {
|
|
179
|
-
name: api.name,
|
|
180
|
-
path: api.path,
|
|
181
|
-
method: api.method,
|
|
182
|
-
description: api.description,
|
|
183
|
-
addonName: api.addonName,
|
|
184
|
-
addonTitle: api.addonTitle
|
|
185
|
-
}
|
|
186
|
-
});
|
|
187
|
-
}
|
|
81
|
+
};
|
|
82
|
+
})
|
|
83
|
+
);
|
|
188
84
|
} catch (error: any) {
|
|
189
|
-
Logger.error({ err: error
|
|
85
|
+
Logger.error({ err: error }, "同步接口批量更新失败");
|
|
190
86
|
}
|
|
191
87
|
}
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* 删除配置中不存在的记录
|
|
196
|
-
*/
|
|
197
|
-
async function deleteObsoleteRecords(helper: any, apiPaths: Set<string>): Promise<void> {
|
|
198
|
-
const allRecords = await helper.getAll({
|
|
199
|
-
table: "addon_admin_api",
|
|
200
|
-
where: { state$gte: 0 }
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
for (const record of allRecords.lists) {
|
|
204
|
-
if (record.path && !apiPaths.has(record.path)) {
|
|
205
|
-
await helper.delForce({
|
|
206
|
-
table: "addon_admin_api",
|
|
207
|
-
where: { id: record.id }
|
|
208
|
-
});
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
88
|
|
|
213
|
-
|
|
214
|
-
* SyncApi 命令主函数
|
|
215
|
-
*/
|
|
216
|
-
export async function syncApiCommand(options: SyncApiOptions = {}): Promise<void> {
|
|
217
|
-
try {
|
|
218
|
-
if (options.plan) {
|
|
219
|
-
Logger.debug("[计划] 同步 API 接口到数据库(plan 模式不执行)");
|
|
220
|
-
return;
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
// 连接数据库(SQL + Redis)
|
|
224
|
-
await Connect.connect();
|
|
225
|
-
|
|
226
|
-
const redisHelper = new RedisHelper();
|
|
227
|
-
const helper = new DbHelper({ redis: redisHelper } as any, Connect.getSql());
|
|
228
|
-
const cacheHelper = new CacheHelper({ db: helper, redis: redisHelper } as any);
|
|
229
|
-
|
|
230
|
-
// 1. 检查表是否存在(addon_admin_api 来自 addon-admin 组件)
|
|
231
|
-
const exists = await helper.tableExists("addon_admin_api");
|
|
232
|
-
|
|
233
|
-
if (!exists) {
|
|
234
|
-
Logger.debug("表 addon_admin_api 不存在,跳过 API 同步(需要安装 addon-admin 组件)");
|
|
235
|
-
return;
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
// 2. 扫描所有 API 文件
|
|
239
|
-
const apis = await scanAllApis();
|
|
240
|
-
const apiPaths = new Set(apis.map((api) => api.path));
|
|
241
|
-
|
|
242
|
-
// 3. 同步 API 数据
|
|
243
|
-
await syncApis(helper, apis);
|
|
244
|
-
|
|
245
|
-
// 4. 删除文件中不存在的接口
|
|
246
|
-
await deleteObsoleteRecords(helper, apiPaths);
|
|
247
|
-
|
|
248
|
-
// 5. 缓存接口数据到 Redis
|
|
89
|
+
if (insData.length > 0) {
|
|
249
90
|
try {
|
|
250
|
-
await
|
|
251
|
-
|
|
252
|
-
|
|
91
|
+
await ctx.db.insBatch(
|
|
92
|
+
tableName,
|
|
93
|
+
insData.map((api) => {
|
|
94
|
+
return {
|
|
95
|
+
name: api.name,
|
|
96
|
+
routePath: api.routePath,
|
|
97
|
+
addonName: api.addonName
|
|
98
|
+
};
|
|
99
|
+
})
|
|
100
|
+
);
|
|
101
|
+
} catch (error: any) {
|
|
102
|
+
Logger.error({ err: error }, "同步接口批量新增失败");
|
|
253
103
|
}
|
|
104
|
+
}
|
|
254
105
|
|
|
255
|
-
|
|
256
|
-
// 说明:role permission set 的成员是 METHOD/path,API 的 method/path 变更会影响所有角色权限。
|
|
106
|
+
if (delData.length > 0) {
|
|
257
107
|
try {
|
|
258
|
-
await
|
|
108
|
+
await ctx.db.delForceBatch(tableName, delData);
|
|
259
109
|
} catch (error: any) {
|
|
260
|
-
|
|
261
|
-
Logger.warn({ err: error }, "API 同步完成,但重建角色权限缓存失败");
|
|
110
|
+
Logger.error({ err: error }, "同步接口批量删除失败");
|
|
262
111
|
}
|
|
263
|
-
} catch (error: any) {
|
|
264
|
-
Logger.error({ err: error }, "API 同步失败");
|
|
265
|
-
throw error;
|
|
266
|
-
} finally {
|
|
267
|
-
await Connect.disconnect();
|
|
268
112
|
}
|
|
113
|
+
// 缓存同步职责已收敛到 syncCache(启动流程单点调用),此处只负责 DB 同步。
|
|
269
114
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { BeflyContext } from "../types/befly.js";
|
|
2
|
+
|
|
3
|
+
export async function syncCache(ctx: Pick<BeflyContext, "cache">): Promise<void> {
|
|
4
|
+
if (!ctx.cache) {
|
|
5
|
+
throw new Error("syncCache: ctx.cache 未初始化(cache 插件未加载或注入失败)");
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
// 1) 缓存接口列表
|
|
9
|
+
await ctx.cache.cacheApis();
|
|
10
|
+
|
|
11
|
+
// 2) 缓存菜单列表
|
|
12
|
+
await ctx.cache.cacheMenus();
|
|
13
|
+
|
|
14
|
+
// 3) 重建角色权限缓存(严格模式下要求 role.apis 必须为 pathname 字符串数组)
|
|
15
|
+
await ctx.cache.rebuildRoleApiPermissions();
|
|
16
|
+
}
|