befly 3.10.18 → 3.11.1
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 +83 -307
- package/dist/befly.config.d.ts +7 -0
- package/{befly.config.ts → dist/befly.config.js} +11 -36
- package/dist/befly.js +15621 -0
- package/dist/befly.min.js +21 -0
- package/dist/checks/checkApi.d.ts +1 -0
- package/{checks/checkApi.ts → dist/checks/checkApi.js} +12 -30
- package/dist/checks/checkHook.d.ts +1 -0
- package/dist/checks/checkHook.js +86 -0
- package/dist/checks/checkMenu.d.ts +7 -0
- package/{checks/checkMenu.ts → dist/checks/checkMenu.js} +18 -53
- package/dist/checks/checkPlugin.d.ts +1 -0
- package/dist/checks/checkPlugin.js +86 -0
- package/dist/checks/checkTable.d.ts +6 -0
- package/{checks/checkTable.ts → dist/checks/checkTable.js} +17 -41
- package/dist/configs/presetFields.d.ts +4 -0
- package/{configs/presetFields.ts → dist/configs/presetFields.js} +1 -1
- package/dist/configs/presetRegexp.d.ts +145 -0
- package/{utils/regex.ts → dist/configs/presetRegexp.js} +8 -31
- package/dist/hooks/auth.d.ts +7 -0
- package/{hooks/auth.ts → dist/hooks/auth.js} +8 -10
- package/dist/hooks/cors.d.ts +11 -0
- package/{hooks/cors.ts → dist/hooks/cors.js} +5 -13
- package/dist/hooks/parser.d.ts +14 -0
- package/{hooks/parser.ts → dist/hooks/parser.js} +31 -45
- package/dist/hooks/permission.d.ts +14 -0
- package/{hooks/permission.ts → dist/hooks/permission.js} +16 -25
- package/dist/hooks/validator.d.ts +11 -0
- package/{hooks/validator.ts → dist/hooks/validator.js} +9 -14
- package/dist/index.d.ts +26 -0
- package/{main.ts → dist/index.js} +61 -100
- package/dist/lib/asyncContext.d.ts +21 -0
- package/dist/lib/asyncContext.js +27 -0
- package/dist/lib/cacheHelper.d.ts +95 -0
- package/{lib/cacheHelper.ts → dist/lib/cacheHelper.js} +45 -105
- package/dist/lib/cacheKeys.d.ts +23 -0
- package/{lib/cacheKeys.ts → dist/lib/cacheKeys.js} +5 -10
- package/dist/lib/cipher.d.ts +153 -0
- package/{lib/cipher.ts → dist/lib/cipher.js} +23 -44
- package/dist/lib/connect.d.ts +91 -0
- package/{lib/connect.ts → dist/lib/connect.js} +47 -88
- package/dist/lib/dbDialect.d.ts +87 -0
- package/{lib/dbDialect.ts → dist/lib/dbDialect.js} +32 -112
- package/dist/lib/dbHelper.d.ts +204 -0
- package/{lib/dbHelper.ts → dist/lib/dbHelper.js} +82 -241
- package/dist/lib/dbUtils.d.ts +68 -0
- package/{lib/dbUtils.ts → dist/lib/dbUtils.js} +51 -126
- package/dist/lib/jwt.d.ts +13 -0
- package/{lib/jwt.ts → dist/lib/jwt.js} +11 -32
- package/dist/lib/logger.d.ts +42 -0
- package/dist/lib/logger.js +1144 -0
- package/dist/lib/redisHelper.d.ts +185 -0
- package/{lib/redisHelper.ts → dist/lib/redisHelper.js} +97 -141
- package/dist/lib/sqlBuilder.d.ts +160 -0
- package/{lib/sqlBuilder.ts → dist/lib/sqlBuilder.js} +132 -278
- package/dist/lib/sqlCheck.d.ts +23 -0
- package/{lib/sqlCheck.ts → dist/lib/sqlCheck.js} +24 -41
- package/dist/lib/validator.d.ts +45 -0
- package/{lib/validator.ts → dist/lib/validator.js} +44 -61
- package/dist/loader/loadApis.d.ts +12 -0
- package/{loader/loadApis.ts → dist/loader/loadApis.js} +10 -20
- package/dist/loader/loadHooks.d.ts +7 -0
- package/dist/loader/loadHooks.js +35 -0
- package/dist/loader/loadPlugins.d.ts +8 -0
- package/{loader/loadPlugins.ts → dist/loader/loadPlugins.js} +14 -26
- package/dist/paths.d.ts +93 -0
- package/{paths.ts → dist/paths.js} +6 -19
- package/dist/plugins/cache.d.ts +16 -0
- package/{plugins/cache.ts → dist/plugins/cache.js} +7 -12
- package/dist/plugins/cipher.d.ts +12 -0
- package/{plugins/cipher.ts → dist/plugins/cipher.js} +4 -6
- package/dist/plugins/config.d.ts +12 -0
- package/dist/plugins/config.js +8 -0
- package/dist/plugins/db.d.ts +16 -0
- package/{plugins/db.ts → dist/plugins/db.js} +11 -17
- package/dist/plugins/jwt.d.ts +12 -0
- package/dist/plugins/jwt.js +12 -0
- package/dist/plugins/logger.d.ts +32 -0
- package/{plugins/logger.ts → dist/plugins/logger.js} +5 -8
- package/dist/plugins/redis.d.ts +16 -0
- package/{plugins/redis.ts → dist/plugins/redis.js} +9 -12
- package/dist/plugins/tool.d.ts +81 -0
- package/{plugins/tool.ts → dist/plugins/tool.js} +9 -30
- package/dist/router/api.d.ts +14 -0
- package/dist/router/api.js +107 -0
- package/dist/router/static.d.ts +9 -0
- package/{router/static.ts → dist/router/static.js} +20 -34
- package/dist/scripts/ensureDist.d.ts +1 -0
- package/dist/scripts/ensureDist.js +296 -0
- package/dist/sync/syncApi.d.ts +3 -0
- package/{sync/syncApi.ts → dist/sync/syncApi.js} +35 -55
- package/dist/sync/syncCache.d.ts +2 -0
- package/{sync/syncCache.ts → dist/sync/syncCache.js} +1 -6
- package/dist/sync/syncDev.d.ts +6 -0
- package/{sync/syncDev.ts → dist/sync/syncDev.js} +29 -62
- package/dist/sync/syncMenu.d.ts +14 -0
- package/{sync/syncMenu.ts → dist/sync/syncMenu.js} +65 -125
- package/dist/sync/syncTable.d.ts +151 -0
- package/{sync/syncTable.ts → dist/sync/syncTable.js} +172 -379
- package/{types → dist/types}/api.d.ts +12 -51
- package/dist/types/api.js +4 -0
- package/{types → dist/types}/befly.d.ts +32 -227
- package/dist/types/befly.js +4 -0
- package/{types → dist/types}/cache.d.ts +7 -15
- package/dist/types/cache.js +4 -0
- package/dist/types/cipher.d.ts +27 -0
- package/dist/types/cipher.js +7 -0
- package/{types → dist/types}/common.d.ts +8 -33
- package/dist/types/common.js +5 -0
- package/{types → dist/types}/context.d.ts +3 -5
- package/dist/types/context.js +4 -0
- package/{types → dist/types}/crypto.d.ts +0 -3
- package/dist/types/crypto.js +4 -0
- package/dist/types/database.d.ts +138 -0
- package/dist/types/database.js +4 -0
- package/dist/types/hook.d.ts +17 -0
- package/dist/types/hook.js +6 -0
- package/dist/types/jwt.d.ts +75 -0
- package/dist/types/jwt.js +4 -0
- package/dist/types/logger.d.ts +59 -0
- package/dist/types/logger.js +6 -0
- package/dist/types/plugin.d.ts +16 -0
- package/dist/types/plugin.js +6 -0
- package/dist/types/redis.d.ts +71 -0
- package/dist/types/redis.js +4 -0
- package/{types/roleApisCache.ts → dist/types/roleApisCache.d.ts} +0 -2
- package/dist/types/roleApisCache.js +8 -0
- package/dist/types/sync.d.ts +92 -0
- package/dist/types/sync.js +4 -0
- package/dist/types/table.d.ts +34 -0
- package/dist/types/table.js +4 -0
- package/dist/types/validate.d.ts +67 -0
- package/dist/types/validate.js +4 -0
- package/dist/utils/calcPerfTime.d.ts +4 -0
- package/{utils/calcPerfTime.ts → dist/utils/calcPerfTime.js} +3 -3
- package/dist/utils/convertBigIntFields.d.ts +11 -0
- package/{utils/convertBigIntFields.ts → dist/utils/convertBigIntFields.js} +5 -9
- package/dist/utils/cors.d.ts +8 -0
- package/{utils/cors.ts → dist/utils/cors.js} +1 -3
- package/dist/utils/disableMenusGlob.d.ts +13 -0
- package/{utils/disableMenusGlob.ts → dist/utils/disableMenusGlob.js} +9 -29
- package/dist/utils/fieldClear.d.ts +11 -0
- package/{utils/fieldClear.ts → dist/utils/fieldClear.js} +15 -33
- package/dist/utils/getClientIp.d.ts +6 -0
- package/{utils/getClientIp.ts → dist/utils/getClientIp.js} +1 -7
- package/dist/utils/importDefault.d.ts +1 -0
- package/dist/utils/importDefault.js +29 -0
- package/dist/utils/isDirentDirectory.d.ts +2 -0
- package/{utils/isDirentDirectory.ts → dist/utils/isDirentDirectory.js} +3 -8
- package/dist/utils/loadMenuConfigs.d.ts +29 -0
- package/{utils/loadMenuConfigs.ts → dist/utils/loadMenuConfigs.js} +66 -52
- package/dist/utils/mergeAndConcat.d.ts +7 -0
- package/dist/utils/mergeAndConcat.js +72 -0
- package/dist/utils/processAtSymbol.d.ts +4 -0
- package/{utils/processFields.ts → dist/utils/processAtSymbol.js} +5 -9
- package/dist/utils/processInfo.d.ts +24 -0
- package/{utils/process.ts → dist/utils/processInfo.js} +2 -18
- package/dist/utils/response.d.ts +20 -0
- package/{utils/response.ts → dist/utils/response.js} +28 -49
- package/dist/utils/scanAddons.d.ts +17 -0
- package/{utils/scanAddons.ts → dist/utils/scanAddons.js} +7 -41
- package/dist/utils/scanConfig.d.ts +26 -0
- package/{utils/scanConfig.ts → dist/utils/scanConfig.js} +28 -66
- package/dist/utils/scanCoreBuiltins.d.ts +3 -0
- package/dist/utils/scanCoreBuiltins.js +65 -0
- package/dist/utils/scanFiles.d.ts +30 -0
- package/{utils/scanFiles.ts → dist/utils/scanFiles.js} +44 -71
- package/dist/utils/scanSources.d.ts +10 -0
- package/dist/utils/scanSources.js +46 -0
- package/dist/utils/sortModules.d.ts +28 -0
- package/{utils/sortModules.ts → dist/utils/sortModules.js} +26 -66
- package/dist/utils/util.d.ts +84 -0
- package/dist/utils/util.js +262 -0
- package/package.json +26 -34
- package/.gitignore +0 -0
- package/bunfig.toml +0 -3
- package/checks/checkHook.ts +0 -48
- package/checks/checkPlugin.ts +0 -48
- package/configs/presetRegexp.ts +0 -225
- package/docs/README.md +0 -98
- package/docs/api/api.md +0 -1921
- package/docs/guide/examples.md +0 -926
- package/docs/guide/quickstart.md +0 -354
- package/docs/hooks/auth.md +0 -38
- package/docs/hooks/cors.md +0 -28
- package/docs/hooks/hook.md +0 -838
- package/docs/hooks/parser.md +0 -19
- package/docs/hooks/rateLimit.md +0 -47
- package/docs/infra/redis.md +0 -628
- package/docs/plugins/cipher.md +0 -61
- package/docs/plugins/database.md +0 -189
- package/docs/plugins/plugin.md +0 -986
- package/docs/reference/addon.md +0 -510
- package/docs/reference/config.md +0 -573
- package/docs/reference/logger.md +0 -495
- package/docs/reference/sync.md +0 -478
- package/docs/reference/table.md +0 -763
- package/docs/reference/validator.md +0 -620
- package/lib/asyncContext.ts +0 -43
- package/lib/logger.ts +0 -811
- package/loader/loadHooks.ts +0 -51
- package/plugins/config.ts +0 -13
- package/plugins/jwt.ts +0 -15
- package/router/api.ts +0 -130
- package/tsconfig.json +0 -8
- package/types/database.d.ts +0 -541
- package/types/hook.d.ts +0 -25
- package/types/jwt.d.ts +0 -118
- package/types/logger.d.ts +0 -65
- package/types/plugin.d.ts +0 -19
- package/types/redis.d.ts +0 -83
- package/types/sync.d.ts +0 -398
- package/types/table.d.ts +0 -216
- package/types/validate.d.ts +0 -69
- package/utils/arrayKeysToCamel.ts +0 -18
- package/utils/configTypes.ts +0 -3
- package/utils/genShortId.ts +0 -12
- package/utils/importDefault.ts +0 -21
- package/utils/keysToCamel.ts +0 -22
- package/utils/keysToSnake.ts +0 -22
- package/utils/pickFields.ts +0 -19
- package/utils/scanSources.ts +0 -64
- package/utils/sqlLog.ts +0 -37
|
@@ -1,90 +1,110 @@
|
|
|
1
|
-
import type { MenuConfig } from "../types/sync.ts";
|
|
2
|
-
import type { AddonInfo } from "./scanAddons.ts";
|
|
3
|
-
import type { ViewDirMeta } from "befly-shared/utils/scanViewsDir";
|
|
4
|
-
|
|
5
1
|
import { existsSync } from "node:fs";
|
|
6
2
|
import { readdir, readFile } from "node:fs/promises";
|
|
7
|
-
|
|
8
|
-
import { cleanDirName, extractDefinePageMetaFromScriptSetup, extractScriptSetupBlock } from "befly-shared/utils/scanViewsDir";
|
|
9
3
|
import { join } from "pathe";
|
|
10
|
-
|
|
11
|
-
import {
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
4
|
+
import { Logger } from "../lib/logger";
|
|
5
|
+
import { isDirentDirectory } from "./isDirentDirectory";
|
|
6
|
+
/**
|
|
7
|
+
* 清理目录名中的数字后缀
|
|
8
|
+
* 如:login_1 → login, index_2 → index
|
|
9
|
+
*/
|
|
10
|
+
export function cleanDirName(name) {
|
|
11
|
+
return String(name).replace(/_\d+$/, "");
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* 只取第一个 <script ... setup ...> 块
|
|
15
|
+
*/
|
|
16
|
+
export function extractScriptSetupBlock(vueContent) {
|
|
17
|
+
const openTag = /<script\b[^>]*\bsetup\b[^>]*>/i.exec(vueContent);
|
|
18
|
+
if (!openTag) {
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
const start = openTag.index + openTag[0].length;
|
|
22
|
+
const closeIndex = vueContent.indexOf("</script>", start);
|
|
23
|
+
if (closeIndex < 0) {
|
|
24
|
+
return null;
|
|
25
|
+
}
|
|
26
|
+
return vueContent.slice(start, closeIndex);
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* 从 <script setup> 中提取 definePage({ meta })
|
|
30
|
+
*
|
|
31
|
+
* 简化约束:
|
|
32
|
+
* - 每个页面只有一个 definePage
|
|
33
|
+
* - title 是纯字符串字面量
|
|
34
|
+
* - order 是数字字面量(可选)
|
|
35
|
+
* - 不考虑变量/表达式/多段 meta 组合
|
|
36
|
+
*/
|
|
37
|
+
export function extractDefinePageMetaFromScriptSetup(scriptSetup) {
|
|
38
|
+
const titleMatch = scriptSetup.match(/definePage\s*\([\s\S]*?meta\s*:\s*\{[\s\S]*?title\s*:\s*(["'`])([^"'`]+)\1/);
|
|
39
|
+
if (!titleMatch) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
const orderMatch = scriptSetup.match(/definePage\s*\([\s\S]*?meta\s*:\s*\{[\s\S]*?order\s*:\s*(\d+)/);
|
|
43
|
+
return {
|
|
44
|
+
title: titleMatch[2],
|
|
45
|
+
order: orderMatch ? Number(orderMatch[1]) : undefined
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
export async function scanViewsDirToMenuConfigs(viewsDir, prefix, parentPath = "") {
|
|
15
49
|
if (!existsSync(viewsDir)) {
|
|
16
50
|
return [];
|
|
17
51
|
}
|
|
18
|
-
|
|
19
|
-
const menus: MenuConfig[] = [];
|
|
52
|
+
const menus = [];
|
|
20
53
|
const entries = await readdir(viewsDir, { withFileTypes: true });
|
|
21
|
-
|
|
22
54
|
for (const entry of entries) {
|
|
23
55
|
if (!isDirentDirectory(viewsDir, entry) || entry.name === "components") {
|
|
24
56
|
continue;
|
|
25
57
|
}
|
|
26
|
-
|
|
27
58
|
const dirPath = join(viewsDir, entry.name);
|
|
28
59
|
const indexVuePath = join(dirPath, "index.vue");
|
|
29
|
-
|
|
30
60
|
if (!existsSync(indexVuePath)) {
|
|
31
61
|
continue;
|
|
32
62
|
}
|
|
33
|
-
|
|
34
|
-
let meta: ViewDirMeta | null = null;
|
|
63
|
+
let meta = null;
|
|
35
64
|
try {
|
|
36
65
|
const content = await readFile(indexVuePath, "utf-8");
|
|
37
|
-
|
|
38
66
|
const scriptSetup = extractScriptSetupBlock(content);
|
|
39
67
|
if (!scriptSetup) {
|
|
40
68
|
Logger.warn({ path: indexVuePath }, "index.vue 缺少 <script setup>,已跳过该目录菜单同步");
|
|
41
69
|
continue;
|
|
42
70
|
}
|
|
43
|
-
|
|
44
71
|
meta = extractDefinePageMetaFromScriptSetup(scriptSetup);
|
|
45
72
|
if (!meta?.title) {
|
|
46
73
|
Logger.warn({ path: indexVuePath }, "index.vue 未声明 definePage({ meta: { title, order? } }),已跳过该目录菜单同步");
|
|
47
74
|
continue;
|
|
48
75
|
}
|
|
49
|
-
}
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
50
78
|
Logger.warn({ err: error, path: indexVuePath }, "读取 index.vue 失败");
|
|
51
79
|
continue;
|
|
52
80
|
}
|
|
53
|
-
|
|
54
81
|
if (!meta?.title) {
|
|
55
82
|
continue;
|
|
56
83
|
}
|
|
57
|
-
|
|
58
84
|
const cleanName = cleanDirName(entry.name);
|
|
59
|
-
let menuPath
|
|
85
|
+
let menuPath;
|
|
60
86
|
if (cleanName === "index") {
|
|
61
87
|
menuPath = parentPath;
|
|
62
|
-
}
|
|
88
|
+
}
|
|
89
|
+
else {
|
|
63
90
|
menuPath = parentPath ? `${parentPath}/${cleanName}` : `/${cleanName}`;
|
|
64
91
|
}
|
|
65
|
-
|
|
66
92
|
const fullPath = prefix ? (menuPath ? `${prefix}${menuPath}` : prefix) : menuPath || "/";
|
|
67
|
-
|
|
68
|
-
const menu: MenuConfig = {
|
|
93
|
+
const menu = {
|
|
69
94
|
name: meta.title,
|
|
70
95
|
path: fullPath,
|
|
71
96
|
sort: meta.order ?? 999999
|
|
72
97
|
};
|
|
73
|
-
|
|
74
98
|
const children = await scanViewsDirToMenuConfigs(dirPath, prefix, menuPath);
|
|
75
99
|
if (children.length > 0) {
|
|
76
100
|
menu.children = children;
|
|
77
101
|
}
|
|
78
|
-
|
|
79
102
|
menus.push(menu);
|
|
80
103
|
}
|
|
81
|
-
|
|
82
104
|
menus.sort((a, b) => (a.sort ?? 999999) - (b.sort ?? 999999));
|
|
83
|
-
|
|
84
105
|
return menus;
|
|
85
106
|
}
|
|
86
|
-
|
|
87
|
-
export function getParentPath(path: string): string {
|
|
107
|
+
export function getParentPath(path) {
|
|
88
108
|
// "/a/b" => "/a"
|
|
89
109
|
// "/a" => ""
|
|
90
110
|
const parts = path.split("/").filter((p) => !!p);
|
|
@@ -93,10 +113,8 @@ export function getParentPath(path: string): string {
|
|
|
93
113
|
}
|
|
94
114
|
return `/${parts.slice(0, -1).join("/")}`;
|
|
95
115
|
}
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
const allMenus: MenuConfig[] = [];
|
|
99
|
-
|
|
116
|
+
export async function loadMenuConfigs(addons) {
|
|
117
|
+
const allMenus = [];
|
|
100
118
|
for (const addon of addons) {
|
|
101
119
|
const adminViewsDirByTopLevel = join(addon.fullPath, "adminViews");
|
|
102
120
|
const adminViewsDirByViews = join(addon.fullPath, "views", "admin");
|
|
@@ -104,7 +122,6 @@ export async function loadMenuConfigs(addons: AddonInfo[]): Promise<MenuConfig[]
|
|
|
104
122
|
if (!adminViewsDir) {
|
|
105
123
|
continue;
|
|
106
124
|
}
|
|
107
|
-
|
|
108
125
|
try {
|
|
109
126
|
const prefix = `/${addon.source}/${addon.name}`;
|
|
110
127
|
const menus = await scanViewsDirToMenuConfigs(adminViewsDir, prefix);
|
|
@@ -113,19 +130,16 @@ export async function loadMenuConfigs(addons: AddonInfo[]): Promise<MenuConfig[]
|
|
|
113
130
|
allMenus.push(menu);
|
|
114
131
|
}
|
|
115
132
|
}
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
"扫描 addon views 目录失败"
|
|
125
|
-
);
|
|
133
|
+
}
|
|
134
|
+
catch (error) {
|
|
135
|
+
Logger.warn({
|
|
136
|
+
err: error,
|
|
137
|
+
addon: addon.name,
|
|
138
|
+
addonSource: addon.source,
|
|
139
|
+
dir: adminViewsDir
|
|
140
|
+
}, "扫描 addon views 目录失败");
|
|
126
141
|
}
|
|
127
142
|
}
|
|
128
|
-
|
|
129
143
|
const menusJsonPath = join(process.cwd(), "menus.json");
|
|
130
144
|
if (existsSync(menusJsonPath)) {
|
|
131
145
|
try {
|
|
@@ -136,10 +150,10 @@ export async function loadMenuConfigs(addons: AddonInfo[]): Promise<MenuConfig[]
|
|
|
136
150
|
allMenus.push(menu);
|
|
137
151
|
}
|
|
138
152
|
}
|
|
139
|
-
}
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
140
155
|
Logger.warn({ err: error }, "读取项目 menus.json 失败");
|
|
141
156
|
}
|
|
142
157
|
}
|
|
143
|
-
|
|
144
158
|
return allMenus;
|
|
145
159
|
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { isPlainObject } from "./util";
|
|
2
|
+
function cloneDeepLoose(value) {
|
|
3
|
+
if (Array.isArray(value)) {
|
|
4
|
+
const arr = [];
|
|
5
|
+
for (const item of value) {
|
|
6
|
+
arr.push(cloneDeepLoose(item));
|
|
7
|
+
}
|
|
8
|
+
return arr;
|
|
9
|
+
}
|
|
10
|
+
if (isPlainObject(value)) {
|
|
11
|
+
const out = {};
|
|
12
|
+
for (const key of Object.keys(value)) {
|
|
13
|
+
out[key] = cloneDeepLoose(value[key]);
|
|
14
|
+
}
|
|
15
|
+
return out;
|
|
16
|
+
}
|
|
17
|
+
return value;
|
|
18
|
+
}
|
|
19
|
+
function mergeInto(target, source) {
|
|
20
|
+
if (source === undefined) {
|
|
21
|
+
return target;
|
|
22
|
+
}
|
|
23
|
+
if (Array.isArray(target) && Array.isArray(source)) {
|
|
24
|
+
for (const item of source) {
|
|
25
|
+
target.push(cloneDeepLoose(item));
|
|
26
|
+
}
|
|
27
|
+
return target;
|
|
28
|
+
}
|
|
29
|
+
if (isPlainObject(target) && isPlainObject(source)) {
|
|
30
|
+
for (const key of Object.keys(source)) {
|
|
31
|
+
const srcVal = source[key];
|
|
32
|
+
if (srcVal === undefined) {
|
|
33
|
+
continue;
|
|
34
|
+
}
|
|
35
|
+
const curVal = target[key];
|
|
36
|
+
if (Array.isArray(curVal) && Array.isArray(srcVal)) {
|
|
37
|
+
const nextArr = [];
|
|
38
|
+
for (const item of curVal) {
|
|
39
|
+
nextArr.push(cloneDeepLoose(item));
|
|
40
|
+
}
|
|
41
|
+
for (const item of srcVal) {
|
|
42
|
+
nextArr.push(cloneDeepLoose(item));
|
|
43
|
+
}
|
|
44
|
+
target[key] = nextArr;
|
|
45
|
+
continue;
|
|
46
|
+
}
|
|
47
|
+
if (isPlainObject(curVal) && isPlainObject(srcVal)) {
|
|
48
|
+
target[key] = mergeInto(cloneDeepLoose(curVal), srcVal);
|
|
49
|
+
continue;
|
|
50
|
+
}
|
|
51
|
+
target[key] = cloneDeepLoose(srcVal);
|
|
52
|
+
}
|
|
53
|
+
return target;
|
|
54
|
+
}
|
|
55
|
+
return cloneDeepLoose(source);
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* 深度合并对象,并对数组执行 concat(保持 scanConfig 现有语义)。
|
|
59
|
+
* - undefined 会被忽略
|
|
60
|
+
* - plain object 深合并
|
|
61
|
+
* - array 与 array 合并为新数组(保持输入不被污染)
|
|
62
|
+
*/
|
|
63
|
+
export function mergeAndConcat(...items) {
|
|
64
|
+
let acc = {};
|
|
65
|
+
for (const item of items) {
|
|
66
|
+
if (item === undefined) {
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
acc = mergeInto(acc, item);
|
|
70
|
+
}
|
|
71
|
+
return acc;
|
|
72
|
+
}
|
|
@@ -1,25 +1,21 @@
|
|
|
1
|
-
import { presetFields } from "../configs/presetFields
|
|
2
|
-
|
|
1
|
+
import { presetFields } from "../configs/presetFields";
|
|
3
2
|
/**
|
|
4
3
|
* 处理字段定义:将 @ 符号引用替换为实际字段定义
|
|
5
4
|
*/
|
|
6
|
-
export function
|
|
7
|
-
if (!fields || typeof fields !== "object")
|
|
8
|
-
|
|
9
|
-
const processed
|
|
5
|
+
export function processAtSymbol(fields, apiName, routePath) {
|
|
6
|
+
if (!fields || typeof fields !== "object")
|
|
7
|
+
return fields;
|
|
8
|
+
const processed = {};
|
|
10
9
|
for (const [key, value] of Object.entries(fields)) {
|
|
11
10
|
if (typeof value === "string" && value.startsWith("@")) {
|
|
12
11
|
if (presetFields[value]) {
|
|
13
12
|
processed[key] = presetFields[value];
|
|
14
13
|
continue;
|
|
15
14
|
}
|
|
16
|
-
|
|
17
15
|
const validKeys = Object.keys(presetFields).join(", ");
|
|
18
16
|
throw new Error(`API [${apiName}] (${routePath}) 字段 [${key}] 引用了未定义的预设字段 "${value}"。可用的预设字段有: ${validKeys}`);
|
|
19
17
|
}
|
|
20
|
-
|
|
21
18
|
processed[key] = value;
|
|
22
19
|
}
|
|
23
|
-
|
|
24
20
|
return processed;
|
|
25
21
|
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 进程角色信息
|
|
3
|
+
*/
|
|
4
|
+
export interface ProcessRole {
|
|
5
|
+
/** 进程角色:primary(主进程)或 worker(工作进程) */
|
|
6
|
+
role: "primary" | "worker";
|
|
7
|
+
/** 实例 ID(PM2 或 Bun Worker) */
|
|
8
|
+
instanceId: string | null;
|
|
9
|
+
/** 运行环境:bun-cluster、pm2-cluster 或 standalone */
|
|
10
|
+
env: "bun-cluster" | "pm2-cluster" | "standalone";
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* 获取当前进程角色信息
|
|
14
|
+
* @returns 进程角色、实例 ID 和运行环境
|
|
15
|
+
*/
|
|
16
|
+
export declare function getProcessRole(): ProcessRole;
|
|
17
|
+
/**
|
|
18
|
+
* 检测当前进程是否为主进程
|
|
19
|
+
* 用于集群模式下避免重复执行同步任务
|
|
20
|
+
* - Bun 集群:BUN_WORKER_ID 为空时是主进程
|
|
21
|
+
* - PM2 集群:PM2_INSTANCE_ID 为 '0' 或不存在时是主进程
|
|
22
|
+
* @returns 是否为主进程
|
|
23
|
+
*/
|
|
24
|
+
export declare function isPrimaryProcess(): boolean;
|
|
@@ -1,23 +1,10 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 进程角色信息
|
|
3
|
-
*/
|
|
4
|
-
export interface ProcessRole {
|
|
5
|
-
/** 进程角色:primary(主进程)或 worker(工作进程) */
|
|
6
|
-
role: "primary" | "worker";
|
|
7
|
-
/** 实例 ID(PM2 或 Bun Worker) */
|
|
8
|
-
instanceId: string | null;
|
|
9
|
-
/** 运行环境:bun-cluster、pm2-cluster 或 standalone */
|
|
10
|
-
env: "bun-cluster" | "pm2-cluster" | "standalone";
|
|
11
|
-
}
|
|
12
|
-
|
|
13
1
|
/**
|
|
14
2
|
* 获取当前进程角色信息
|
|
15
3
|
* @returns 进程角色、实例 ID 和运行环境
|
|
16
4
|
*/
|
|
17
|
-
export function getProcessRole()
|
|
5
|
+
export function getProcessRole() {
|
|
18
6
|
const bunWorkerId = process.env.BUN_WORKER_ID;
|
|
19
7
|
const pm2InstanceId = process.env.PM2_INSTANCE_ID;
|
|
20
|
-
|
|
21
8
|
// Bun 集群模式
|
|
22
9
|
if (bunWorkerId !== undefined) {
|
|
23
10
|
return {
|
|
@@ -26,7 +13,6 @@ export function getProcessRole(): ProcessRole {
|
|
|
26
13
|
env: "bun-cluster"
|
|
27
14
|
};
|
|
28
15
|
}
|
|
29
|
-
|
|
30
16
|
// PM2 集群模式
|
|
31
17
|
if (pm2InstanceId !== undefined) {
|
|
32
18
|
return {
|
|
@@ -35,7 +21,6 @@ export function getProcessRole(): ProcessRole {
|
|
|
35
21
|
env: "pm2-cluster"
|
|
36
22
|
};
|
|
37
23
|
}
|
|
38
|
-
|
|
39
24
|
// 单进程模式
|
|
40
25
|
return {
|
|
41
26
|
role: "primary",
|
|
@@ -43,7 +28,6 @@ export function getProcessRole(): ProcessRole {
|
|
|
43
28
|
env: "standalone"
|
|
44
29
|
};
|
|
45
30
|
}
|
|
46
|
-
|
|
47
31
|
/**
|
|
48
32
|
* 检测当前进程是否为主进程
|
|
49
33
|
* 用于集群模式下避免重复执行同步任务
|
|
@@ -51,6 +35,6 @@ export function getProcessRole(): ProcessRole {
|
|
|
51
35
|
* - PM2 集群:PM2_INSTANCE_ID 为 '0' 或不存在时是主进程
|
|
52
36
|
* @returns 是否为主进程
|
|
53
37
|
*/
|
|
54
|
-
export function isPrimaryProcess()
|
|
38
|
+
export function isPrimaryProcess() {
|
|
55
39
|
return getProcessRole().role === "primary";
|
|
56
40
|
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { RequestContext } from "../types/context";
|
|
2
|
+
/**
|
|
3
|
+
* 创建错误响应(专用于 Hook 中间件)
|
|
4
|
+
* 在钩子中提前拦截请求时使用
|
|
5
|
+
* @param ctx - 请求上下文
|
|
6
|
+
* @param msg - 错误消息
|
|
7
|
+
* @param code - 错误码,默认 1
|
|
8
|
+
* @param data - 附加数据,默认 null
|
|
9
|
+
* @param detail - 详细信息,用于标记具体提示位置,默认 null
|
|
10
|
+
* @param reasonCode - 拦截原因标识(用于统计/聚合),默认 null
|
|
11
|
+
* @returns Response 对象
|
|
12
|
+
*/
|
|
13
|
+
export declare function ErrorResponse(ctx: RequestContext, msg: string, code?: number, data?: any, detail?: any, reasonCode?: string | null): Response;
|
|
14
|
+
/**
|
|
15
|
+
* 创建最终响应(专用于 API 路由结尾)
|
|
16
|
+
* 自动处理 ctx.response/ctx.result,并记录请求日志
|
|
17
|
+
* @param ctx - 请求上下文
|
|
18
|
+
* @returns Response 对象
|
|
19
|
+
*/
|
|
20
|
+
export declare function FinalResponse(ctx: RequestContext): Response;
|
|
@@ -1,7 +1,4 @@
|
|
|
1
|
-
import
|
|
2
|
-
|
|
3
|
-
import { Logger } from "../lib/logger.ts";
|
|
4
|
-
|
|
1
|
+
import { Logger } from "../lib/logger";
|
|
5
2
|
/**
|
|
6
3
|
* 创建错误响应(专用于 Hook 中间件)
|
|
7
4
|
* 在钩子中提前拦截请求时使用
|
|
@@ -13,62 +10,48 @@ import { Logger } from "../lib/logger.ts";
|
|
|
13
10
|
* @param reasonCode - 拦截原因标识(用于统计/聚合),默认 null
|
|
14
11
|
* @returns Response 对象
|
|
15
12
|
*/
|
|
16
|
-
export function ErrorResponse(ctx
|
|
13
|
+
export function ErrorResponse(ctx, msg, code = 1, data = null, detail = null, reasonCode = null) {
|
|
17
14
|
// 记录拦截日志
|
|
18
15
|
if (ctx.requestId) {
|
|
19
16
|
// requestId/route/user/duration 等字段由 ALS 统一注入,避免在 msg 中重复拼接
|
|
20
|
-
Logger.info(
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
reasonCode: reasonCode,
|
|
25
|
-
code: code,
|
|
26
|
-
detail: detail
|
|
27
|
-
},
|
|
28
|
-
"request blocked"
|
|
29
|
-
);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
return Response.json(
|
|
33
|
-
{
|
|
17
|
+
Logger.info({
|
|
18
|
+
event: "request_blocked",
|
|
19
|
+
reason: msg,
|
|
20
|
+
reasonCode: reasonCode,
|
|
34
21
|
code: code,
|
|
35
|
-
msg: msg,
|
|
36
|
-
data: data,
|
|
37
22
|
detail: detail
|
|
38
|
-
},
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
23
|
+
}, "request blocked");
|
|
24
|
+
}
|
|
25
|
+
return Response.json({
|
|
26
|
+
code: code,
|
|
27
|
+
msg: msg,
|
|
28
|
+
data: data,
|
|
29
|
+
detail: detail
|
|
30
|
+
}, {
|
|
31
|
+
headers: ctx.corsHeaders
|
|
32
|
+
});
|
|
43
33
|
}
|
|
44
|
-
|
|
45
34
|
/**
|
|
46
35
|
* 创建最终响应(专用于 API 路由结尾)
|
|
47
36
|
* 自动处理 ctx.response/ctx.result,并记录请求日志
|
|
48
37
|
* @param ctx - 请求上下文
|
|
49
38
|
* @returns Response 对象
|
|
50
39
|
*/
|
|
51
|
-
export function FinalResponse(ctx
|
|
40
|
+
export function FinalResponse(ctx) {
|
|
52
41
|
// 记录请求日志
|
|
53
42
|
if (ctx.api && ctx.requestId) {
|
|
54
43
|
// requestId/route/user/duration 等字段由 ALS 统一注入,避免在 msg 中重复拼接
|
|
55
|
-
Logger.info(
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
},
|
|
59
|
-
"request done"
|
|
60
|
-
);
|
|
44
|
+
Logger.info({
|
|
45
|
+
event: "request_done"
|
|
46
|
+
}, "request done");
|
|
61
47
|
}
|
|
62
|
-
|
|
63
48
|
// 1. 如果已经有 response,直接返回
|
|
64
49
|
if (ctx.response) {
|
|
65
50
|
return ctx.response;
|
|
66
51
|
}
|
|
67
|
-
|
|
68
52
|
// 2. 如果有 result,格式化为响应
|
|
69
53
|
if (ctx.result !== undefined) {
|
|
70
54
|
let result = ctx.result;
|
|
71
|
-
|
|
72
55
|
// 如果是字符串,自动包裹为成功响应
|
|
73
56
|
if (typeof result === "string") {
|
|
74
57
|
result = {
|
|
@@ -85,7 +68,6 @@ export function FinalResponse(ctx: RequestContext): Response {
|
|
|
85
68
|
};
|
|
86
69
|
}
|
|
87
70
|
}
|
|
88
|
-
|
|
89
71
|
// 处理 BigInt 序列化问题
|
|
90
72
|
if (result && typeof result === "object") {
|
|
91
73
|
const jsonString = JSON.stringify(result, (key, value) => (typeof value === "bigint" ? value.toString() : value));
|
|
@@ -95,21 +77,18 @@ export function FinalResponse(ctx: RequestContext): Response {
|
|
|
95
77
|
"Content-Type": "application/json"
|
|
96
78
|
}
|
|
97
79
|
});
|
|
98
|
-
}
|
|
80
|
+
}
|
|
81
|
+
else {
|
|
99
82
|
return Response.json(result, {
|
|
100
83
|
headers: ctx.corsHeaders
|
|
101
84
|
});
|
|
102
85
|
}
|
|
103
86
|
}
|
|
104
|
-
|
|
105
87
|
// 3. 默认响应:没有生成响应
|
|
106
|
-
return Response.json(
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
headers: ctx.corsHeaders
|
|
113
|
-
}
|
|
114
|
-
);
|
|
88
|
+
return Response.json({
|
|
89
|
+
code: 1,
|
|
90
|
+
msg: "未生成响应"
|
|
91
|
+
}, {
|
|
92
|
+
headers: ctx.corsHeaders
|
|
93
|
+
});
|
|
115
94
|
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export type AddonSource = "addon" | "app";
|
|
2
|
+
export interface AddonInfo {
|
|
3
|
+
/** addon 来源 */
|
|
4
|
+
source: AddonSource;
|
|
5
|
+
/** addon 来源中文名 */
|
|
6
|
+
sourceName: string;
|
|
7
|
+
/** addon 名称(目录名,通常是 demo/admin 等) */
|
|
8
|
+
name: string;
|
|
9
|
+
/** camelCase(name) */
|
|
10
|
+
camelName: string;
|
|
11
|
+
/** addon 根目录绝对路径 */
|
|
12
|
+
rootDir: string;
|
|
13
|
+
/** 兼容字段:历史上使用 fullPath 表示 rootDir */
|
|
14
|
+
fullPath: string;
|
|
15
|
+
}
|
|
16
|
+
/** 扫描 node_modules/@befly-addon + 项目 addons/(项目优先级更高) */
|
|
17
|
+
export declare const scanAddons: () => AddonInfo[];
|
|
@@ -1,55 +1,25 @@
|
|
|
1
1
|
import { existsSync, readdirSync } from "node:fs";
|
|
2
|
-
|
|
3
|
-
import { camelCase } from "es-toolkit/string";
|
|
4
2
|
import { join, resolve } from "pathe";
|
|
5
|
-
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
export type AddonSource = "addon" | "app";
|
|
10
|
-
|
|
11
|
-
export interface AddonInfo {
|
|
12
|
-
/** addon 来源 */
|
|
13
|
-
source: AddonSource;
|
|
14
|
-
|
|
15
|
-
/** addon 来源中文名 */
|
|
16
|
-
sourceName: string;
|
|
17
|
-
|
|
18
|
-
/** addon 名称(目录名,通常是 demo/admin 等) */
|
|
19
|
-
name: string;
|
|
20
|
-
|
|
21
|
-
/** camelCase(name) */
|
|
22
|
-
camelName: string;
|
|
23
|
-
|
|
24
|
-
/** addon 根目录绝对路径 */
|
|
25
|
-
rootDir: string;
|
|
26
|
-
|
|
27
|
-
/** 兼容字段:历史上使用 fullPath 表示 rootDir */
|
|
28
|
-
fullPath: string;
|
|
29
|
-
}
|
|
30
|
-
|
|
3
|
+
import { appAddonsDir, appDir } from "../paths";
|
|
4
|
+
import { isDirentDirectory } from "./isDirentDirectory";
|
|
5
|
+
import { camelCase } from "./util";
|
|
31
6
|
/** 扫描 node_modules/@befly-addon + 项目 addons/(项目优先级更高) */
|
|
32
|
-
export const scanAddons = ()
|
|
33
|
-
const addonMap = new Map
|
|
34
|
-
|
|
35
|
-
const scanBaseDir = (baseDir: string, source: AddonSource, sourceName: string) => {
|
|
7
|
+
export const scanAddons = () => {
|
|
8
|
+
const addonMap = new Map();
|
|
9
|
+
const scanBaseDir = (baseDir, source, sourceName) => {
|
|
36
10
|
if (!existsSync(baseDir)) {
|
|
37
11
|
return;
|
|
38
12
|
}
|
|
39
|
-
|
|
40
13
|
const entries = readdirSync(baseDir, { withFileTypes: true });
|
|
41
14
|
for (const entry of entries) {
|
|
42
15
|
if (entry.name.startsWith("_")) {
|
|
43
16
|
continue;
|
|
44
17
|
}
|
|
45
|
-
|
|
46
18
|
if (!isDirentDirectory(baseDir, entry)) {
|
|
47
19
|
continue;
|
|
48
20
|
}
|
|
49
|
-
|
|
50
21
|
const rootDir = resolve(baseDir, entry.name);
|
|
51
|
-
|
|
52
|
-
const info: AddonInfo = {
|
|
22
|
+
const info = {
|
|
53
23
|
source: source,
|
|
54
24
|
sourceName: sourceName,
|
|
55
25
|
name: entry.name,
|
|
@@ -57,16 +27,12 @@ export const scanAddons = (): AddonInfo[] => {
|
|
|
57
27
|
rootDir: rootDir,
|
|
58
28
|
fullPath: rootDir
|
|
59
29
|
};
|
|
60
|
-
|
|
61
30
|
addonMap.set(entry.name, info);
|
|
62
31
|
}
|
|
63
32
|
};
|
|
64
|
-
|
|
65
33
|
// node_modules 中的 @befly-addon
|
|
66
34
|
scanBaseDir(join(appDir, "node_modules", "@befly-addon"), "addon", "组件");
|
|
67
|
-
|
|
68
35
|
// 项目本地 addons(同名覆盖 node_modules)
|
|
69
36
|
scanBaseDir(appAddonsDir, "app", "项目");
|
|
70
|
-
|
|
71
37
|
return Array.from(addonMap.values());
|
|
72
38
|
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 加载配置选项
|
|
3
|
+
* - 该类型为 scanConfig 专用:按“就近放置”原则直接与实现放在同文件,避免拆到单独的 *Types.ts 造成目录噪音。
|
|
4
|
+
*/
|
|
5
|
+
export interface LoadConfigOptions {
|
|
6
|
+
/** 当前工作目录,默认 process.cwd() */
|
|
7
|
+
cwd?: string;
|
|
8
|
+
/** 目录数组:要搜索的目录路径(相对于 cwd) */
|
|
9
|
+
dirs: string[];
|
|
10
|
+
/** 文件数组:要匹配的文件名 */
|
|
11
|
+
files: string[];
|
|
12
|
+
/** 文件扩展名,默认 ['.js', '.ts', '.json'] */
|
|
13
|
+
extensions?: string[];
|
|
14
|
+
/** 加载模式:'first' = 返回第一个找到的配置(默认),'merge' = 合并所有配置 */
|
|
15
|
+
mode?: "merge" | "first";
|
|
16
|
+
/** 指定要提取的字段路径数组,如 ['menus', 'database.host'],为空则返回完整对象 */
|
|
17
|
+
paths?: string[];
|
|
18
|
+
/** 默认配置,会与找到的配置合并(优先级最低) */
|
|
19
|
+
defaults?: Record<string, any>;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* 扫描并合并配置文件(矩阵搜索:dirs × files)
|
|
23
|
+
* @param options - 加载选项
|
|
24
|
+
* @returns 合并后的配置对象(或第一个找到的配置)
|
|
25
|
+
*/
|
|
26
|
+
export declare function scanConfig(options: LoadConfigOptions): Promise<Record<string, any>>;
|