befly 3.17.0 → 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 +19 -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/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 -16413
- package/befly.min.js +0 -72
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 进程角色信息
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* 获取当前进程角色信息
|
|
7
|
+
* @returns 进程角色、实例 ID 和运行环境
|
|
8
|
+
*/
|
|
9
|
+
export function getProcessRole(env) {
|
|
10
|
+
const runtimeEnv = env || {};
|
|
11
|
+
|
|
12
|
+
const bunWorkerId = runtimeEnv["BUN_WORKER_ID"];
|
|
13
|
+
const pm2InstanceId = runtimeEnv["PM2_INSTANCE_ID"];
|
|
14
|
+
|
|
15
|
+
// Bun 集群模式
|
|
16
|
+
if (bunWorkerId !== undefined) {
|
|
17
|
+
return {
|
|
18
|
+
role: bunWorkerId === "" ? "primary" : "worker",
|
|
19
|
+
instanceId: bunWorkerId || "0",
|
|
20
|
+
env: "bun-cluster"
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// PM2 集群模式
|
|
25
|
+
if (pm2InstanceId !== undefined) {
|
|
26
|
+
return {
|
|
27
|
+
role: pm2InstanceId === "0" ? "primary" : "worker",
|
|
28
|
+
instanceId: pm2InstanceId,
|
|
29
|
+
env: "pm2-cluster"
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// 单进程模式
|
|
34
|
+
return {
|
|
35
|
+
role: "primary",
|
|
36
|
+
instanceId: null,
|
|
37
|
+
env: "standalone"
|
|
38
|
+
};
|
|
39
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { isMinLengthString } from "./is.js";
|
|
2
|
+
import RegexAliases from "../configs/regexpAlias.json";
|
|
3
|
+
|
|
4
|
+
const regexCache = new Map();
|
|
5
|
+
|
|
6
|
+
export function getRegex(name) {
|
|
7
|
+
if (name.startsWith("@")) {
|
|
8
|
+
const alias = name.slice(1);
|
|
9
|
+
return RegexAliases[alias] || name;
|
|
10
|
+
}
|
|
11
|
+
return name;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function parseRegexLiteral(pattern) {
|
|
15
|
+
const trimmed = String(pattern || "").trim();
|
|
16
|
+
if (!isMinLengthString(trimmed, 2)) return null;
|
|
17
|
+
if (!trimmed.startsWith("/")) return null;
|
|
18
|
+
|
|
19
|
+
const lastSlash = trimmed.lastIndexOf("/");
|
|
20
|
+
if (lastSlash <= 0) return null;
|
|
21
|
+
|
|
22
|
+
const flags = trimmed.slice(lastSlash + 1);
|
|
23
|
+
if (flags !== "" && !/^[gimsuy]+$/.test(flags)) return null;
|
|
24
|
+
|
|
25
|
+
return { source: trimmed.slice(1, lastSlash), flags: flags };
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export function getCompiledRegex(pattern, flags) {
|
|
29
|
+
const literal = parseRegexLiteral(pattern);
|
|
30
|
+
const regexStr = literal ? literal.source : getRegex(pattern);
|
|
31
|
+
const useFlags = literal && literal.flags !== "" ? literal.flags : flags || "";
|
|
32
|
+
const cacheKey = `${regexStr}:${useFlags}`;
|
|
33
|
+
|
|
34
|
+
let cached = regexCache.get(cacheKey);
|
|
35
|
+
if (!cached) {
|
|
36
|
+
cached = new RegExp(regexStr, useFlags);
|
|
37
|
+
regexCache.set(cacheKey, cached);
|
|
38
|
+
}
|
|
39
|
+
return cached;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function matchRegex(value, pattern) {
|
|
43
|
+
return getCompiledRegex(pattern).test(value);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export function clearRegexCache() {
|
|
47
|
+
regexCache.clear();
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function getRegexCacheSize() {
|
|
51
|
+
return regexCache.size;
|
|
52
|
+
}
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { Logger } from "../lib/logger.js";
|
|
2
|
+
import { isString } from "./is.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 创建错误响应(专用于 Hook 中间件)
|
|
6
|
+
* 在钩子中提前拦截请求时使用
|
|
7
|
+
* @param ctx - 请求上下文
|
|
8
|
+
* @param msg - 错误消息
|
|
9
|
+
* @param code - 错误码,默认 1
|
|
10
|
+
* @param data - 附加数据,默认 null
|
|
11
|
+
* @param detail - 详细信息,用于标记具体提示位置,默认 null
|
|
12
|
+
* @param reasonCode - 拦截原因标识(用于统计/聚合),默认 null
|
|
13
|
+
* @returns Response 对象
|
|
14
|
+
*/
|
|
15
|
+
export function ErrorResponse(ctx, msg, code = 1, data = null, detail = null, reasonCode = null) {
|
|
16
|
+
// 记录拦截日志
|
|
17
|
+
if (ctx.requestId) {
|
|
18
|
+
// requestId/apiPath/user/duration 等字段由调用方显式写入日志上下文,避免在 msg 中重复拼接
|
|
19
|
+
Logger.info("请求已拦截", {
|
|
20
|
+
event: "request_blocked",
|
|
21
|
+
reason: msg,
|
|
22
|
+
reasonCode: reasonCode,
|
|
23
|
+
code: code,
|
|
24
|
+
detail: detail
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
return Response.json(
|
|
29
|
+
{
|
|
30
|
+
code: code,
|
|
31
|
+
msg: msg,
|
|
32
|
+
data: data,
|
|
33
|
+
detail: detail
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
headers: ctx.corsHeaders
|
|
37
|
+
}
|
|
38
|
+
);
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* 创建最终响应(专用于 API 路由结尾)
|
|
43
|
+
* 自动处理 ctx.response/ctx.result,并记录请求日志
|
|
44
|
+
* @param ctx - 请求上下文
|
|
45
|
+
* @returns Response 对象
|
|
46
|
+
*/
|
|
47
|
+
export function FinalResponse(ctx) {
|
|
48
|
+
// 记录请求日志
|
|
49
|
+
if (ctx.apiPath && ctx.requestId) {
|
|
50
|
+
// requestId/apiPath/user/duration 等字段由调用方显式写入日志上下文,避免在 msg 中重复拼接
|
|
51
|
+
Logger.info("请求完成", {
|
|
52
|
+
event: "request_done"
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// 1. 如果已经有 response,直接返回
|
|
57
|
+
if (ctx.response) {
|
|
58
|
+
return ctx.response;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// 2. 如果有 result,格式化为响应
|
|
62
|
+
if (ctx.result !== undefined) {
|
|
63
|
+
let result = ctx.result;
|
|
64
|
+
|
|
65
|
+
// 如果是字符串,自动包裹为成功响应
|
|
66
|
+
if (isString(result)) {
|
|
67
|
+
result = {
|
|
68
|
+
code: 0,
|
|
69
|
+
msg: result
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
// 如果是对象,自动补充 code: 0
|
|
73
|
+
else if (result && typeof result === "object") {
|
|
74
|
+
if (!("code" in result)) {
|
|
75
|
+
const merged = { code: 0 };
|
|
76
|
+
for (const key of Object.keys(result)) {
|
|
77
|
+
merged[key] = result[key];
|
|
78
|
+
}
|
|
79
|
+
result = merged;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// 处理 BigInt 序列化问题
|
|
84
|
+
if (result && typeof result === "object") {
|
|
85
|
+
const jsonString = JSON.stringify(result, (key, value) => (typeof value === "bigint" ? value.toString() : value));
|
|
86
|
+
const headers = {};
|
|
87
|
+
const cors = ctx.corsHeaders;
|
|
88
|
+
if (cors && typeof cors === "object") {
|
|
89
|
+
for (const key of Object.keys(cors)) {
|
|
90
|
+
headers[key] = cors[key];
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
headers["Content-Type"] = "application/json";
|
|
94
|
+
return new Response(jsonString, {
|
|
95
|
+
headers: headers
|
|
96
|
+
});
|
|
97
|
+
} else {
|
|
98
|
+
return Response.json(result, {
|
|
99
|
+
headers: ctx.corsHeaders
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// 3. 默认响应:没有生成响应
|
|
105
|
+
return Response.json(
|
|
106
|
+
{
|
|
107
|
+
code: 1,
|
|
108
|
+
msg: "未生成响应"
|
|
109
|
+
},
|
|
110
|
+
{
|
|
111
|
+
headers: ctx.corsHeaders
|
|
112
|
+
}
|
|
113
|
+
);
|
|
114
|
+
}
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
|
|
3
|
+
import { relative, normalize, parse, join } from "pathe";
|
|
4
|
+
|
|
5
|
+
import { importDefault } from "./importDefault.js";
|
|
6
|
+
import { isPlainObject } from "./is.js";
|
|
7
|
+
|
|
8
|
+
// [ hooks ]-112 {
|
|
9
|
+
// source: "core",
|
|
10
|
+
// type: "hook",
|
|
11
|
+
// filePath: "D:/codes/befly/packages/core/hooks/auth.js",
|
|
12
|
+
// relativePath: "auth",
|
|
13
|
+
// fileName: "auth",
|
|
14
|
+
// apiPath: "/api/core/auth",
|
|
15
|
+
// name: "auth",
|
|
16
|
+
// deps: [ "cors" ],
|
|
17
|
+
// handler: [AsyncFunction: handler],
|
|
18
|
+
// }
|
|
19
|
+
// 🔥[ plugins ]-112 {
|
|
20
|
+
// source: "core",
|
|
21
|
+
// type: "plugin",
|
|
22
|
+
// filePath: "D:/codes/befly/packages/core/plugins/cache.js",
|
|
23
|
+
// relativePath: "cache",
|
|
24
|
+
// fileName: "cache",
|
|
25
|
+
// apiPath: "/api/core/cache",
|
|
26
|
+
// name: "cache",
|
|
27
|
+
// deps: [ "logger", "redis", "mysql" ],
|
|
28
|
+
// handler: [AsyncFunction: handler],
|
|
29
|
+
// }
|
|
30
|
+
// 🔥[ tables ]-112 {
|
|
31
|
+
// source: "core",
|
|
32
|
+
// type: "table",
|
|
33
|
+
// filePath: "D:/codes/befly/packages/core/tables/admin.json",
|
|
34
|
+
// relativePath: "admin",
|
|
35
|
+
// fileName: "admin",
|
|
36
|
+
// apiPath: "/api/core/admin",
|
|
37
|
+
// "lastLoginTime", "lastLoginIp"
|
|
38
|
+
// ],
|
|
39
|
+
// name: "admin",
|
|
40
|
+
// }
|
|
41
|
+
// 🔥[ apis ]-112 {
|
|
42
|
+
// source: "core",
|
|
43
|
+
// type: "api",
|
|
44
|
+
// filePath: "D:/codes/befly/packages/core/apis/sysConfig/all.js",
|
|
45
|
+
// relativePath: "sysConfig/all",
|
|
46
|
+
// fileName: "all",
|
|
47
|
+
// apiPath: "/api/core/sysConfig/all",
|
|
48
|
+
// name: "all",
|
|
49
|
+
// method: "POST",
|
|
50
|
+
// body: "none",
|
|
51
|
+
// auth: true,
|
|
52
|
+
// fields: {},
|
|
53
|
+
// required: [],
|
|
54
|
+
// handler: [AsyncFunction: handler],
|
|
55
|
+
// }
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* 扫描指定目录下的文件
|
|
59
|
+
* @param dir 目录路径
|
|
60
|
+
* @param source 文件来源(app/core)
|
|
61
|
+
* @param type 文件类型(api/table/plugin/hook)
|
|
62
|
+
* @param pattern Glob模式
|
|
63
|
+
*/
|
|
64
|
+
export async function scanFiles(dir, source, type, pattern) {
|
|
65
|
+
try {
|
|
66
|
+
const glob = new Bun.Glob(pattern);
|
|
67
|
+
if (!existsSync(dir)) return [];
|
|
68
|
+
|
|
69
|
+
const results = [];
|
|
70
|
+
|
|
71
|
+
const files = await glob.scan({
|
|
72
|
+
cwd: dir,
|
|
73
|
+
onlyFiles: true,
|
|
74
|
+
absolute: true,
|
|
75
|
+
followSymlinks: true
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
for await (const file of files) {
|
|
79
|
+
// 使用 pathe.normalize 统一路径分隔符为 /
|
|
80
|
+
const filePath = normalize(file);
|
|
81
|
+
const parsedFile = parse(filePath);
|
|
82
|
+
|
|
83
|
+
// 获取文件名(去除扩展名)
|
|
84
|
+
const fileName = parsedFile.name;
|
|
85
|
+
|
|
86
|
+
// 计算相对路径(去除扩展名)
|
|
87
|
+
const parsedRelativePath = parse(relative(dir, filePath));
|
|
88
|
+
const relativePath = parsedRelativePath.dir ? join(parsedRelativePath.dir, parsedRelativePath.name) : parsedRelativePath.name;
|
|
89
|
+
|
|
90
|
+
// 固定默认过滤(不可关闭):忽略下划线开头的文件/目录
|
|
91
|
+
if (relativePath.split("/").some((part) => part.startsWith("_"))) continue;
|
|
92
|
+
const content = await importDefault(filePath, {});
|
|
93
|
+
const contentObj = isPlainObject(content) ? content : {};
|
|
94
|
+
|
|
95
|
+
const base = {
|
|
96
|
+
source: source,
|
|
97
|
+
type: type,
|
|
98
|
+
filePath: filePath,
|
|
99
|
+
relativePath: relativePath,
|
|
100
|
+
fileName: fileName,
|
|
101
|
+
apiPath: `/api/${source}/${relativePath}`
|
|
102
|
+
};
|
|
103
|
+
if (type === "table") {
|
|
104
|
+
base["fieldsDef"] = contentObj;
|
|
105
|
+
}
|
|
106
|
+
if (type === "api") {
|
|
107
|
+
base["name"] = "";
|
|
108
|
+
}
|
|
109
|
+
if (type !== "table") {
|
|
110
|
+
for (const [key, value] of Object.entries(contentObj)) {
|
|
111
|
+
if (base[key]) continue;
|
|
112
|
+
base[key] = value;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
results.push(base);
|
|
116
|
+
}
|
|
117
|
+
return results;
|
|
118
|
+
} catch (error) {
|
|
119
|
+
throw new Error(`扫描失败: source=${source} type=${type} dir=${dir} pattern=${pattern}`, {
|
|
120
|
+
cause: error,
|
|
121
|
+
code: "runtime"
|
|
122
|
+
});
|
|
123
|
+
}
|
|
124
|
+
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { join } from "pathe";
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
//
|
|
5
|
+
coreTableDir,
|
|
6
|
+
appTableDir,
|
|
7
|
+
corePluginDir,
|
|
8
|
+
appPluginDir,
|
|
9
|
+
coreHookDir,
|
|
10
|
+
appHookDir,
|
|
11
|
+
coreApiDir,
|
|
12
|
+
appApiDir
|
|
13
|
+
} from "../paths.js";
|
|
14
|
+
import { scanFiles } from "./scanFiles.js";
|
|
15
|
+
|
|
16
|
+
export const scanSources = async () => {
|
|
17
|
+
const apis = [];
|
|
18
|
+
const plugins = [];
|
|
19
|
+
const hooks = [];
|
|
20
|
+
const tables = [];
|
|
21
|
+
|
|
22
|
+
// 处理表格
|
|
23
|
+
const allCoreTables = await scanFiles(coreTableDir, "core", "table", "*.json");
|
|
24
|
+
for (const item of allCoreTables) {
|
|
25
|
+
tables.push(item);
|
|
26
|
+
}
|
|
27
|
+
const allAppTables = await scanFiles(appTableDir, "app", "table", "*.json");
|
|
28
|
+
for (const item of allAppTables) {
|
|
29
|
+
tables.push(item);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// 处理插件
|
|
33
|
+
const allCorePlugins = await scanFiles(corePluginDir, "core", "plugin", "*.js");
|
|
34
|
+
for (const item of allCorePlugins) {
|
|
35
|
+
plugins.push(item);
|
|
36
|
+
}
|
|
37
|
+
const allAppPlugins = await scanFiles(appPluginDir, "app", "plugin", "*.js");
|
|
38
|
+
for (const item of allAppPlugins) {
|
|
39
|
+
plugins.push(item);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// 处理钩子
|
|
43
|
+
const allCoreHooks = await scanFiles(coreHookDir, "core", "hook", "*.js");
|
|
44
|
+
for (const item of allCoreHooks) {
|
|
45
|
+
hooks.push(item);
|
|
46
|
+
}
|
|
47
|
+
const allAppHooks = await scanFiles(appHookDir, "app", "hook", "*.js");
|
|
48
|
+
for (const item of allAppHooks) {
|
|
49
|
+
hooks.push(item);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// 处理接口
|
|
53
|
+
const allCoreApis = await scanFiles(coreApiDir, "core", "api", "**/*.js");
|
|
54
|
+
for (const item of allCoreApis) {
|
|
55
|
+
apis.push(item);
|
|
56
|
+
}
|
|
57
|
+
const allAppApis = await scanFiles(appApiDir, "app", "api", "**/*.js");
|
|
58
|
+
for (const item of allAppApis) {
|
|
59
|
+
apis.push(item);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
hooks: hooks,
|
|
64
|
+
plugins: plugins,
|
|
65
|
+
apis: apis,
|
|
66
|
+
tables: tables
|
|
67
|
+
};
|
|
68
|
+
};
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { Logger } from "../lib/logger.js";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* 按 deps 拓扑排序 scanSources 扫描得到的插件/钩子。
|
|
5
|
+
*
|
|
6
|
+
* 说明:
|
|
7
|
+
* - 输入为 scanSources/scanFiles 的条目数组:每个条目包含 fileName 与 deps。
|
|
8
|
+
* - deps 里的字符串会与 fileName 匹配。
|
|
9
|
+
* - 若出现:重复 name、缺失依赖、循环依赖,则返回 false。
|
|
10
|
+
*/
|
|
11
|
+
export function sortModules(items) {
|
|
12
|
+
const result = [];
|
|
13
|
+
const visited = new Set();
|
|
14
|
+
const visiting = new Set();
|
|
15
|
+
|
|
16
|
+
const nameToItem = {};
|
|
17
|
+
let isPass = true;
|
|
18
|
+
|
|
19
|
+
// 1) 建表 + 重名检查
|
|
20
|
+
for (const item of items) {
|
|
21
|
+
if (nameToItem[item.fileName]) {
|
|
22
|
+
Logger.error("模块 名称重复,无法根据 deps 唯一定位", null, {
|
|
23
|
+
name: item.fileName,
|
|
24
|
+
first: nameToItem[item.fileName],
|
|
25
|
+
second: item
|
|
26
|
+
});
|
|
27
|
+
isPass = false;
|
|
28
|
+
continue;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
nameToItem[item.fileName] = item;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
if (!isPass) return false;
|
|
35
|
+
|
|
36
|
+
// 2) 依赖存在性检查
|
|
37
|
+
for (const item of items) {
|
|
38
|
+
for (const dep of item.deps) {
|
|
39
|
+
if (!nameToItem[dep]) {
|
|
40
|
+
Logger.error("模块 依赖未找到", null, { module: item.fileName, dependency: dep });
|
|
41
|
+
isPass = false;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
if (!isPass) return false;
|
|
47
|
+
|
|
48
|
+
// 3) 拓扑排序(DFS)
|
|
49
|
+
const visit = (name) => {
|
|
50
|
+
if (visited.has(name)) return;
|
|
51
|
+
if (visiting.has(name)) {
|
|
52
|
+
Logger.error("模块 循环依赖", null, { module: name });
|
|
53
|
+
isPass = false;
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const item = nameToItem[name];
|
|
58
|
+
if (!item) return;
|
|
59
|
+
|
|
60
|
+
visiting.add(name);
|
|
61
|
+
for (const dep of item.deps) {
|
|
62
|
+
visit(dep);
|
|
63
|
+
}
|
|
64
|
+
visiting.delete(name);
|
|
65
|
+
|
|
66
|
+
visited.add(name);
|
|
67
|
+
result.push(item);
|
|
68
|
+
};
|
|
69
|
+
|
|
70
|
+
for (const item of items) {
|
|
71
|
+
visit(item.fileName);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return isPass ? result : false;
|
|
75
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function toSessionTtlSeconds(ttlDays) {
|
|
2
|
+
if (typeof ttlDays === "number" && Number.isFinite(ttlDays) && ttlDays > 0) {
|
|
3
|
+
return Math.floor(ttlDays * 24 * 60 * 60);
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
if (typeof ttlDays === "string") {
|
|
7
|
+
const parsed = Number(ttlDays.trim());
|
|
8
|
+
if (Number.isFinite(parsed) && parsed > 0) {
|
|
9
|
+
return Math.floor(parsed * 24 * 60 * 60);
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
return 7 * 24 * 60 * 60;
|
|
14
|
+
}
|