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
package/paths.js
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Befly 框架路径配置
|
|
3
|
+
*
|
|
4
|
+
* 提供统一的路径常量,供整个框架使用
|
|
5
|
+
* 所有路径常量采用具名导出方式,避免通过对象访问
|
|
6
|
+
*
|
|
7
|
+
* 路径分类:
|
|
8
|
+
* - root* 系列:Core 框架内部路径(packages/core/*)
|
|
9
|
+
* - app* 系列:用户项目路径(process.cwd()/*)
|
|
10
|
+
*
|
|
11
|
+
*/
|
|
12
|
+
|
|
13
|
+
import { fileURLToPath } from "node:url";
|
|
14
|
+
|
|
15
|
+
import { dirname, join, normalize } from "pathe";
|
|
16
|
+
|
|
17
|
+
// 当前文件的路径信息
|
|
18
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
19
|
+
const __dirname = dirname(__filename);
|
|
20
|
+
|
|
21
|
+
// ==================== Core 框架路径 ====================
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Core 框架根目录
|
|
25
|
+
* @description packages/core/
|
|
26
|
+
*/
|
|
27
|
+
export const coreDir = __dirname;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Core 框架 dist 目录
|
|
31
|
+
* @description 源码态为 packages/core/dist;dist 运行态为 packages/core/dist
|
|
32
|
+
*/
|
|
33
|
+
export const coreDistDir = normalize(__dirname).endsWith("/dist") ? __dirname : join(__dirname, "dist");
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* Core 框架检查目录
|
|
37
|
+
* @description packages/core/checks/
|
|
38
|
+
* @usage 存放启动检查模块(返回 boolean 的 default 函数)
|
|
39
|
+
*/
|
|
40
|
+
export const coreCheckDir = join(__dirname, "checks");
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* Core 框架插件目录
|
|
44
|
+
* @description packages/core/plugins/
|
|
45
|
+
* @usage 存放内置插件(mysql, logger, redis, tool 等)
|
|
46
|
+
*/
|
|
47
|
+
export const corePluginDir = join(__dirname, "plugins");
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Core 框架钩子目录
|
|
51
|
+
* @description packages/core/hooks/
|
|
52
|
+
* @usage 存放内置钩子(auth, cors, parser 等)
|
|
53
|
+
*/
|
|
54
|
+
export const coreHookDir = join(__dirname, "hooks");
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Core 框架 API 目录
|
|
58
|
+
* @description packages/core/apis/
|
|
59
|
+
* @usage 存放框架级别的 API 接口
|
|
60
|
+
*/
|
|
61
|
+
export const coreApiDir = join(__dirname, "apis");
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Core 框架表定义目录
|
|
65
|
+
* @description packages/core/tables/
|
|
66
|
+
* @usage 存放框架核心表定义(JSON 格式)
|
|
67
|
+
*/
|
|
68
|
+
export const coreTableDir = join(__dirname, "tables");
|
|
69
|
+
|
|
70
|
+
// ==================== 用户项目路径 ====================
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* 项目根目录
|
|
74
|
+
* @description process.cwd()
|
|
75
|
+
* @usage 用户项目的根目录
|
|
76
|
+
*/
|
|
77
|
+
export const appDir = process.cwd();
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* 项目检查目录
|
|
81
|
+
* @description {appDir}/checks/
|
|
82
|
+
* @usage 存放用户自定义启动检查模块
|
|
83
|
+
*/
|
|
84
|
+
export const appCheckDir = join(appDir, "checks");
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* 项目插件目录
|
|
88
|
+
* @description {appDir}/plugins/
|
|
89
|
+
* @usage 存放用户自定义插件
|
|
90
|
+
*/
|
|
91
|
+
export const appPluginDir = join(appDir, "plugins");
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* 项目钩子目录
|
|
95
|
+
* @description {appDir}/hooks/
|
|
96
|
+
* @usage 存放用户自定义钩子
|
|
97
|
+
*/
|
|
98
|
+
export const appHookDir = join(appDir, "hooks");
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* 项目 API 目录
|
|
102
|
+
* @description {appDir}/apis/
|
|
103
|
+
* @usage 存放用户业务 API 接口
|
|
104
|
+
*/
|
|
105
|
+
export const appApiDir = join(appDir, "apis");
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* 项目表定义目录
|
|
109
|
+
* @description {appDir}/tables/
|
|
110
|
+
* @usage 存放用户业务表定义(JSON 格式)
|
|
111
|
+
*/
|
|
112
|
+
export const appTableDir = join(appDir, "tables");
|
package/plugins/cache.js
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 缓存插件 - JavaScript 版本
|
|
3
|
+
* 负责在服务器启动时缓存接口、菜单和角色权限到 Redis
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { CacheHelper } from "../lib/cacheHelper.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 缓存插件
|
|
10
|
+
*/
|
|
11
|
+
export default {
|
|
12
|
+
deps: ["logger", "redis", "mysql"],
|
|
13
|
+
async handler(befly) {
|
|
14
|
+
return new CacheHelper({ mysql: befly.mysql, redis: befly.redis });
|
|
15
|
+
}
|
|
16
|
+
};
|
package/plugins/email.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 邮件插件
|
|
3
|
+
* 提供邮件发送功能,支持 SMTP 配置
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { EmailHelper } from "../lib/emailHelper.js";
|
|
7
|
+
|
|
8
|
+
/** 默认配置 */
|
|
9
|
+
const defaultConfig = {
|
|
10
|
+
host: "smtp.qq.com",
|
|
11
|
+
port: 465,
|
|
12
|
+
secure: true,
|
|
13
|
+
user: "",
|
|
14
|
+
pass: "",
|
|
15
|
+
fromName: "Befly System"
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export default {
|
|
19
|
+
deps: ["mysql", "logger", "config"],
|
|
20
|
+
async handler(befly) {
|
|
21
|
+
// 从 befly.config.email 获取配置
|
|
22
|
+
const emailConfigRaw = (befly.config ? befly.config["email"] : undefined) || {};
|
|
23
|
+
const emailConfig = Object.assign({}, defaultConfig, emailConfigRaw);
|
|
24
|
+
|
|
25
|
+
return new EmailHelper(befly, emailConfig);
|
|
26
|
+
}
|
|
27
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 日志插件
|
|
3
|
+
* 提供全局日志功能
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Logger } from "../lib/logger.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 日志插件
|
|
10
|
+
*/
|
|
11
|
+
export default {
|
|
12
|
+
deps: [],
|
|
13
|
+
async handler(befly) {
|
|
14
|
+
// 配置 Logger
|
|
15
|
+
if (befly.config && befly.config.logger) {
|
|
16
|
+
Logger.configure(befly.config.logger);
|
|
17
|
+
}
|
|
18
|
+
return Logger;
|
|
19
|
+
}
|
|
20
|
+
};
|
package/plugins/mysql.js
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 数据库插件
|
|
3
|
+
* 初始化数据库连接和 SQL 管理器
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Connect } from "../lib/connect.js";
|
|
7
|
+
import { DbHelper } from "../lib/dbHelper/index.js";
|
|
8
|
+
import { Logger } from "../lib/logger.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 数据库插件
|
|
12
|
+
*/
|
|
13
|
+
export default {
|
|
14
|
+
deps: ["logger", "redis"],
|
|
15
|
+
async handler(befly) {
|
|
16
|
+
try {
|
|
17
|
+
// 创建数据库管理器实例
|
|
18
|
+
const dbManager = new DbHelper({
|
|
19
|
+
redis: befly.redis,
|
|
20
|
+
dbName: befly.config?.mysql?.database,
|
|
21
|
+
sql: Connect.getMysql(),
|
|
22
|
+
idMode: befly.config?.mysql?.idMode
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
return dbManager;
|
|
26
|
+
} catch (error) {
|
|
27
|
+
Logger.error("数据库初始化失败", error, { runMode: befly.config?.runMode });
|
|
28
|
+
throw new Error("数据库初始化失败", {
|
|
29
|
+
cause: error,
|
|
30
|
+
code: "runtime",
|
|
31
|
+
subsystem: "db",
|
|
32
|
+
operation: "init"
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
};
|
package/plugins/redis.js
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Redis 插件
|
|
3
|
+
* 初始化 Redis 连接和助手工具
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Connect } from "../lib/connect.js";
|
|
7
|
+
import { Logger } from "../lib/logger.js";
|
|
8
|
+
import { RedisHelper } from "../lib/redisHelper.js";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Redis 插件
|
|
12
|
+
*/
|
|
13
|
+
export default {
|
|
14
|
+
deps: ["logger"],
|
|
15
|
+
async handler(befly) {
|
|
16
|
+
const env = befly.config?.runMode;
|
|
17
|
+
const redisPrefix = befly.config?.redis?.prefix;
|
|
18
|
+
try {
|
|
19
|
+
// 启动期已建立 Redis 连接;这里仅校验连接存在
|
|
20
|
+
Connect.getRedis();
|
|
21
|
+
|
|
22
|
+
// 返回 RedisHelper 实例
|
|
23
|
+
return new RedisHelper(redisPrefix);
|
|
24
|
+
} catch (error) {
|
|
25
|
+
Logger.error("Redis 初始化失败", error, { env: env });
|
|
26
|
+
throw new Error("Redis 初始化失败", {
|
|
27
|
+
cause: error,
|
|
28
|
+
code: "runtime",
|
|
29
|
+
subsystem: "redis",
|
|
30
|
+
operation: "init"
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
};
|
package/plugins/tool.js
ADDED
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 工具插件
|
|
3
|
+
* 提供常用的工具函数
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { isNumber, isString } from "../utils/is.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* 成功响应
|
|
10
|
+
* @param msg - 消息
|
|
11
|
+
* @param data - 数据(可选)
|
|
12
|
+
* @param other - 其他字段(可选)
|
|
13
|
+
* @returns 成功响应对象
|
|
14
|
+
*/
|
|
15
|
+
export function Yes(msg, data = {}, other = {}) {
|
|
16
|
+
const out = {
|
|
17
|
+
code: 0,
|
|
18
|
+
msg: msg,
|
|
19
|
+
data: data
|
|
20
|
+
};
|
|
21
|
+
if (other && typeof other === "object") {
|
|
22
|
+
for (const key of Object.keys(other)) {
|
|
23
|
+
out[key] = other[key];
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return out;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* 失败响应
|
|
31
|
+
* @param msg - 消息
|
|
32
|
+
* @param data - 数据(可选)
|
|
33
|
+
* @param other - 其他字段(可选)
|
|
34
|
+
* @returns 失败响应对象
|
|
35
|
+
*/
|
|
36
|
+
export function No(msg, data = null, other = {}) {
|
|
37
|
+
const out = {
|
|
38
|
+
code: 1,
|
|
39
|
+
msg: msg,
|
|
40
|
+
data: data
|
|
41
|
+
};
|
|
42
|
+
if (other && typeof other === "object") {
|
|
43
|
+
for (const key of Object.keys(other)) {
|
|
44
|
+
out[key] = other[key];
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return out;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* 统一响应函数
|
|
52
|
+
*
|
|
53
|
+
* 自动识别数据类型并设置正确的 Content-Type:
|
|
54
|
+
* - 对象 → application/json
|
|
55
|
+
* - 字符串 → text/plain
|
|
56
|
+
* - 可通过 options.contentType 手动指定
|
|
57
|
+
*
|
|
58
|
+
* @param ctx 请求上下文
|
|
59
|
+
* @param data 响应数据(对象或字符串)
|
|
60
|
+
* @param options 响应选项
|
|
61
|
+
* @returns Response 对象
|
|
62
|
+
*/
|
|
63
|
+
export function Raw(ctx, data, options = {}) {
|
|
64
|
+
const status = isNumber(options.status) ? options.status : 200;
|
|
65
|
+
const contentType = options.contentType;
|
|
66
|
+
const headers = options.headers || {};
|
|
67
|
+
|
|
68
|
+
// 自动判断 Content-Type
|
|
69
|
+
let finalContentType = contentType;
|
|
70
|
+
let body;
|
|
71
|
+
|
|
72
|
+
if (isString(data)) {
|
|
73
|
+
// 字符串类型
|
|
74
|
+
body = data;
|
|
75
|
+
if (!finalContentType) {
|
|
76
|
+
// 自动判断:XML 或纯文本
|
|
77
|
+
finalContentType = data.trim().startsWith("<") ? "application/xml" : "text/plain";
|
|
78
|
+
}
|
|
79
|
+
} else {
|
|
80
|
+
// JSON 序列化(对象/数组/原始值均可)
|
|
81
|
+
body = JSON.stringify(data);
|
|
82
|
+
finalContentType = finalContentType || "application/json";
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// 合并响应头
|
|
86
|
+
const responseHeaders = {};
|
|
87
|
+
const corsHeaders = ctx.corsHeaders;
|
|
88
|
+
if (corsHeaders && typeof corsHeaders === "object") {
|
|
89
|
+
for (const key of Object.keys(corsHeaders)) {
|
|
90
|
+
responseHeaders[key] = corsHeaders[key];
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
responseHeaders["Content-Type"] = finalContentType;
|
|
94
|
+
if (headers && typeof headers === "object") {
|
|
95
|
+
for (const key of Object.keys(headers)) {
|
|
96
|
+
responseHeaders[key] = headers[key];
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
return new Response(body, {
|
|
101
|
+
status: status,
|
|
102
|
+
headers: responseHeaders
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* 计算 md5(hex)
|
|
108
|
+
* @param value 输入字符串
|
|
109
|
+
* @returns md5 hex
|
|
110
|
+
*/
|
|
111
|
+
export function md5(value) {
|
|
112
|
+
if (!isString(value) || value.length < 1) {
|
|
113
|
+
throw new Error("md5 输入必须是非空字符串", {
|
|
114
|
+
cause: null,
|
|
115
|
+
code: "validation",
|
|
116
|
+
subsystem: "tool",
|
|
117
|
+
operation: "md5"
|
|
118
|
+
});
|
|
119
|
+
}
|
|
120
|
+
const hasher = new Bun.CryptoHasher("md5");
|
|
121
|
+
hasher.update(value);
|
|
122
|
+
return hasher.digest("hex");
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* 计算 sha256(hex)
|
|
127
|
+
* @param value 输入字符串
|
|
128
|
+
* @returns sha256 hex
|
|
129
|
+
*/
|
|
130
|
+
export function sha256(value) {
|
|
131
|
+
if (!isString(value) || value.length < 1) {
|
|
132
|
+
throw new Error("sha256 输入必须是非空字符串", {
|
|
133
|
+
cause: null,
|
|
134
|
+
code: "validation",
|
|
135
|
+
subsystem: "tool",
|
|
136
|
+
operation: "sha256"
|
|
137
|
+
});
|
|
138
|
+
}
|
|
139
|
+
const hasher = new Bun.CryptoHasher("sha256");
|
|
140
|
+
hasher.update(value);
|
|
141
|
+
return hasher.digest("hex");
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
export default {
|
|
145
|
+
deps: [],
|
|
146
|
+
handler: () => {
|
|
147
|
+
return {
|
|
148
|
+
Yes: Yes,
|
|
149
|
+
No: No,
|
|
150
|
+
Raw: Raw,
|
|
151
|
+
md5: md5,
|
|
152
|
+
sha256: sha256
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
};
|
package/router/api.js
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* API路由处理器
|
|
3
|
+
* 处理 /api/* 路径的请求
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Logger } from "../lib/logger.js";
|
|
7
|
+
// 相对导入
|
|
8
|
+
import { getClientIp } from "../utils/getClientIp.js";
|
|
9
|
+
import { FinalResponse } from "../utils/response.js";
|
|
10
|
+
import { genShortId } from "../utils/util.js";
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* API处理器工厂函数
|
|
14
|
+
* @param apis - API路由映射表
|
|
15
|
+
* @param hooks - 钩子列表
|
|
16
|
+
* @param context - 应用上下文
|
|
17
|
+
*/
|
|
18
|
+
export function apiHandler(apis, hooks, context) {
|
|
19
|
+
return async (req, server) => {
|
|
20
|
+
// 1. 生成请求 ID
|
|
21
|
+
const requestId = genShortId();
|
|
22
|
+
|
|
23
|
+
// 2. 创建请求上下文
|
|
24
|
+
const url = new URL(req.url);
|
|
25
|
+
// 只用接口路径做存在性判断与匹配:不要把 method 拼进 key
|
|
26
|
+
// 说明:apisMap 的 key 来源于 scanFiles/loadApis 生成的 path(例如 /api/core/xxx)
|
|
27
|
+
|
|
28
|
+
const clientIp = getClientIp(req, server);
|
|
29
|
+
|
|
30
|
+
const now = Date.now();
|
|
31
|
+
|
|
32
|
+
const ctx = {
|
|
33
|
+
method: req.method,
|
|
34
|
+
body: {},
|
|
35
|
+
req: req,
|
|
36
|
+
now: now,
|
|
37
|
+
ip: clientIp,
|
|
38
|
+
headers: req.headers,
|
|
39
|
+
requestId: requestId,
|
|
40
|
+
corsHeaders: {
|
|
41
|
+
"X-Request-ID": requestId
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
const apiData = apis[url.pathname];
|
|
46
|
+
if (apiData) {
|
|
47
|
+
ctx.apiPath = apiData.apiPath;
|
|
48
|
+
ctx.apiName = apiData.name;
|
|
49
|
+
ctx.filePath = apiData.filePath;
|
|
50
|
+
ctx.handler = apiData.handler;
|
|
51
|
+
ctx.method = apiData.method;
|
|
52
|
+
ctx.body = apiData.body;
|
|
53
|
+
ctx.auth = apiData.auth;
|
|
54
|
+
ctx.fields = apiData.fields;
|
|
55
|
+
ctx.required = apiData.required;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
try {
|
|
59
|
+
// 4. 串联执行所有钩子
|
|
60
|
+
for (const hook of hooks) {
|
|
61
|
+
await hook.handler(context, ctx);
|
|
62
|
+
|
|
63
|
+
// 如果钩子已经设置了 response,停止执行
|
|
64
|
+
if (ctx.response) {
|
|
65
|
+
return ctx.response;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// hooks 全部通过后记录请求日志(拦截请求仅由 ErrorResponse 记录)
|
|
70
|
+
if (ctx.handler && req.method !== "OPTIONS") {
|
|
71
|
+
const logData = {
|
|
72
|
+
event: "request",
|
|
73
|
+
requestId: requestId,
|
|
74
|
+
method: req.method,
|
|
75
|
+
apiPath: ctx.apiPath,
|
|
76
|
+
apiName: ctx.apiName,
|
|
77
|
+
ip: clientIp,
|
|
78
|
+
now: now,
|
|
79
|
+
userId: ctx.userId,
|
|
80
|
+
nickname: ctx.nickname,
|
|
81
|
+
roleCode: ctx.roleCode,
|
|
82
|
+
roleType: ctx.roleType
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
if (ctx.body && Object.keys(ctx.body).length > 0) {
|
|
86
|
+
logData["body"] = ctx.body;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
Logger.info("请求", logData);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// 5. 执行 API handler
|
|
93
|
+
if (!ctx.handler) {
|
|
94
|
+
if (req.method !== "OPTIONS") {
|
|
95
|
+
ctx.response = Response.json(
|
|
96
|
+
{
|
|
97
|
+
code: 1,
|
|
98
|
+
msg: "接口不存在"
|
|
99
|
+
},
|
|
100
|
+
{
|
|
101
|
+
headers: ctx.corsHeaders
|
|
102
|
+
}
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
} else if (ctx.handler) {
|
|
106
|
+
const result = await ctx.handler(context, ctx);
|
|
107
|
+
|
|
108
|
+
if (result instanceof Response) {
|
|
109
|
+
ctx.response = result;
|
|
110
|
+
} else {
|
|
111
|
+
ctx.result = result;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// 7. 返回响应(自动处理 response/result/日志)
|
|
116
|
+
return FinalResponse(ctx);
|
|
117
|
+
} catch (err) {
|
|
118
|
+
// 全局错误处理
|
|
119
|
+
const errorPath = ctx.apiPath ? ctx.apiPath : req.url;
|
|
120
|
+
Logger.error("请求错误", err, {
|
|
121
|
+
path: errorPath,
|
|
122
|
+
requestId: requestId,
|
|
123
|
+
method: req.method,
|
|
124
|
+
apiPath: ctx.apiPath,
|
|
125
|
+
apiName: ctx.apiName,
|
|
126
|
+
ip: clientIp,
|
|
127
|
+
now: now,
|
|
128
|
+
userId: ctx.userId,
|
|
129
|
+
nickname: ctx.nickname,
|
|
130
|
+
roleCode: ctx.roleCode,
|
|
131
|
+
roleType: ctx.roleType
|
|
132
|
+
});
|
|
133
|
+
ctx.result = {
|
|
134
|
+
code: 1,
|
|
135
|
+
msg: "内部服务错误"
|
|
136
|
+
};
|
|
137
|
+
return FinalResponse(ctx);
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
}
|
package/router/static.js
ADDED
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 静态文件路由处理器
|
|
3
|
+
* 处理 /* 路径的静态文件请求
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
// 外部依赖
|
|
7
|
+
import { join } from "pathe";
|
|
8
|
+
|
|
9
|
+
import { Logger } from "../lib/logger.js";
|
|
10
|
+
// 相对导入
|
|
11
|
+
import { appDir } from "../paths.js";
|
|
12
|
+
import { setCorsOptions } from "../utils/cors.js";
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* 静态文件处理器工厂
|
|
16
|
+
*/
|
|
17
|
+
export function staticHandler(corsConfig = undefined) {
|
|
18
|
+
return async (req) => {
|
|
19
|
+
// 设置 CORS 响应头
|
|
20
|
+
const corsHeaders = setCorsOptions(req, corsConfig);
|
|
21
|
+
|
|
22
|
+
const url = new URL(req.url);
|
|
23
|
+
const filePath = join(appDir, "public", url.pathname);
|
|
24
|
+
|
|
25
|
+
try {
|
|
26
|
+
// OPTIONS预检请求
|
|
27
|
+
if (req.method === "OPTIONS") {
|
|
28
|
+
return new Response(null, {
|
|
29
|
+
status: 204,
|
|
30
|
+
headers: corsHeaders
|
|
31
|
+
});
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const file = Bun.file(filePath);
|
|
35
|
+
if (await file.exists()) {
|
|
36
|
+
const headers = {};
|
|
37
|
+
if (corsHeaders && typeof corsHeaders === "object") {
|
|
38
|
+
for (const key of Object.keys(corsHeaders)) {
|
|
39
|
+
headers[key] = corsHeaders[key];
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
headers["Content-Type"] = file.type || "application/octet-stream";
|
|
43
|
+
return new Response(file, {
|
|
44
|
+
headers: headers
|
|
45
|
+
});
|
|
46
|
+
} else {
|
|
47
|
+
return Response.json(
|
|
48
|
+
{
|
|
49
|
+
code: 1,
|
|
50
|
+
msg: "文件未找到"
|
|
51
|
+
},
|
|
52
|
+
{
|
|
53
|
+
headers: corsHeaders
|
|
54
|
+
}
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
} catch (error) {
|
|
58
|
+
Logger.error("静态文件处理失败", error);
|
|
59
|
+
|
|
60
|
+
return Response.json(
|
|
61
|
+
{
|
|
62
|
+
code: 1,
|
|
63
|
+
msg: "文件读取失败"
|
|
64
|
+
},
|
|
65
|
+
{
|
|
66
|
+
headers: corsHeaders
|
|
67
|
+
}
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
};
|
|
71
|
+
}
|
package/sql/admin.sql
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
CREATE TABLE IF NOT EXISTS `admin` (
|
|
2
|
+
`id` BIGINT NOT NULL,
|
|
3
|
+
`nickname` VARCHAR(50) NOT NULL DEFAULT '',
|
|
4
|
+
`username` VARCHAR(30) NOT NULL DEFAULT '',
|
|
5
|
+
`password` VARCHAR(500) NOT NULL DEFAULT '',
|
|
6
|
+
`email` VARCHAR(100) NOT NULL DEFAULT '',
|
|
7
|
+
`phone` VARCHAR(20) NOT NULL DEFAULT '',
|
|
8
|
+
`avatar` VARCHAR(500) NOT NULL DEFAULT '',
|
|
9
|
+
`role_code` VARCHAR(50) NOT NULL DEFAULT '',
|
|
10
|
+
`role_type` VARCHAR(5) NOT NULL DEFAULT 'user',
|
|
11
|
+
`last_login_time` BIGINT NOT NULL DEFAULT 0,
|
|
12
|
+
`last_login_ip` VARCHAR(50) NOT NULL DEFAULT '',
|
|
13
|
+
`state` TINYINT NOT NULL DEFAULT 1,
|
|
14
|
+
`created_at` BIGINT NOT NULL DEFAULT 0,
|
|
15
|
+
`updated_at` BIGINT NOT NULL DEFAULT 0,
|
|
16
|
+
`deleted_at` BIGINT NULL DEFAULT NULL,
|
|
17
|
+
PRIMARY KEY (`id`)
|
|
18
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
package/sql/api.sql
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
CREATE TABLE IF NOT EXISTS `api` (
|
|
2
|
+
`id` BIGINT NOT NULL,
|
|
3
|
+
`name` VARCHAR(100) NOT NULL DEFAULT '',
|
|
4
|
+
`auth` VARCHAR(200) NOT NULL DEFAULT '',
|
|
5
|
+
`path` VARCHAR(200) NOT NULL DEFAULT '',
|
|
6
|
+
`parent_path` VARCHAR(200) NOT NULL DEFAULT '',
|
|
7
|
+
`state` TINYINT NOT NULL DEFAULT 1,
|
|
8
|
+
`created_at` BIGINT NOT NULL DEFAULT 0,
|
|
9
|
+
`updated_at` BIGINT NOT NULL DEFAULT 0,
|
|
10
|
+
`deleted_at` BIGINT NULL DEFAULT NULL,
|
|
11
|
+
PRIMARY KEY (`id`)
|
|
12
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
package/sql/dict.sql
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
CREATE TABLE IF NOT EXISTS `dict` (
|
|
2
|
+
`id` BIGINT NOT NULL,
|
|
3
|
+
`type_code` VARCHAR(50) NOT NULL DEFAULT '',
|
|
4
|
+
`key` VARCHAR(50) NOT NULL DEFAULT '',
|
|
5
|
+
`label` VARCHAR(100) NOT NULL DEFAULT '',
|
|
6
|
+
`sort` INT NOT NULL DEFAULT 0,
|
|
7
|
+
`remark` VARCHAR(200) NOT NULL DEFAULT '',
|
|
8
|
+
`state` TINYINT NOT NULL DEFAULT 1,
|
|
9
|
+
`created_at` BIGINT NOT NULL DEFAULT 0,
|
|
10
|
+
`updated_at` BIGINT NOT NULL DEFAULT 0,
|
|
11
|
+
`deleted_at` BIGINT NULL DEFAULT NULL,
|
|
12
|
+
PRIMARY KEY (`id`)
|
|
13
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
package/sql/dictType.sql
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
CREATE TABLE IF NOT EXISTS `dict_type` (
|
|
2
|
+
`id` BIGINT NOT NULL,
|
|
3
|
+
`code` VARCHAR(50) NOT NULL DEFAULT '',
|
|
4
|
+
`name` VARCHAR(50) NOT NULL DEFAULT '',
|
|
5
|
+
`description` VARCHAR(200) NOT NULL DEFAULT '',
|
|
6
|
+
`sort` INT NOT NULL DEFAULT 0,
|
|
7
|
+
`state` TINYINT NOT NULL DEFAULT 1,
|
|
8
|
+
`created_at` BIGINT NOT NULL DEFAULT 0,
|
|
9
|
+
`updated_at` BIGINT NOT NULL DEFAULT 0,
|
|
10
|
+
`deleted_at` BIGINT NULL DEFAULT NULL,
|
|
11
|
+
PRIMARY KEY (`id`)
|
|
12
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|