befly 2.3.3 → 3.0.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/apis/health/info.ts +64 -0
- package/apis/tool/tokenCheck.ts +51 -0
- package/bin/befly.ts +202 -0
- package/checks/conflict.ts +408 -0
- package/checks/{table.js → table.ts} +139 -61
- package/config/env.ts +218 -0
- package/config/reserved.ts +96 -0
- package/main.ts +101 -0
- package/package.json +44 -8
- package/plugins/{db.js → db.ts} +24 -11
- package/plugins/logger.ts +28 -0
- package/plugins/redis.ts +51 -0
- package/plugins/tool.ts +34 -0
- package/scripts/syncDb/apply.ts +171 -0
- package/scripts/syncDb/constants.ts +70 -0
- package/scripts/syncDb/ddl.ts +182 -0
- package/scripts/syncDb/helpers.ts +172 -0
- package/scripts/syncDb/index.ts +215 -0
- package/scripts/syncDb/schema.ts +199 -0
- package/scripts/syncDb/sqlite.ts +50 -0
- package/scripts/syncDb/state.ts +104 -0
- package/scripts/syncDb/table.ts +204 -0
- package/scripts/syncDb/tableCreate.ts +142 -0
- package/scripts/syncDb/tests/constants.test.ts +104 -0
- package/scripts/syncDb/tests/ddl.test.ts +134 -0
- package/scripts/syncDb/tests/helpers.test.ts +70 -0
- package/scripts/syncDb/types.ts +92 -0
- package/scripts/syncDb/version.ts +73 -0
- package/scripts/syncDb.ts +9 -0
- package/scripts/{syncDev.js → syncDev.ts} +41 -25
- package/system.ts +149 -0
- package/tables/_common.json +21 -0
- package/tables/admin.json +10 -0
- package/tsconfig.json +58 -0
- package/types/api.d.ts +246 -0
- package/types/befly.d.ts +234 -0
- package/types/common.d.ts +215 -0
- package/types/context.ts +167 -0
- package/types/crypto.d.ts +23 -0
- package/types/database.d.ts +278 -0
- package/types/index.d.ts +16 -0
- package/types/index.ts +459 -0
- package/types/jwt.d.ts +99 -0
- package/types/logger.d.ts +43 -0
- package/types/plugin.d.ts +109 -0
- package/types/redis.d.ts +44 -0
- package/types/tool.d.ts +67 -0
- package/types/validator.d.ts +45 -0
- package/utils/addonHelper.ts +60 -0
- package/utils/api.ts +23 -0
- package/utils/{colors.js → colors.ts} +79 -21
- package/utils/crypto.ts +308 -0
- package/utils/datetime.ts +51 -0
- package/utils/dbHelper.ts +142 -0
- package/utils/errorHandler.ts +68 -0
- package/utils/index.ts +46 -0
- package/utils/jwt.ts +493 -0
- package/utils/logger.ts +284 -0
- package/utils/objectHelper.ts +68 -0
- package/utils/pluginHelper.ts +62 -0
- package/utils/redisHelper.ts +338 -0
- package/utils/response.ts +38 -0
- package/utils/{sqlBuilder.js → sqlBuilder.ts} +233 -97
- package/utils/sqlHelper.ts +447 -0
- package/utils/tableHelper.ts +167 -0
- package/utils/tool.ts +230 -0
- package/utils/typeHelper.ts +101 -0
- package/utils/validate.ts +451 -0
- package/utils/{xml.js → xml.ts} +100 -74
- package/.npmrc +0 -3
- package/.prettierignore +0 -2
- package/.prettierrc +0 -11
- package/apis/health/info.js +0 -49
- package/apis/tool/tokenCheck.js +0 -29
- package/bin/befly.js +0 -109
- package/config/env.js +0 -64
- package/main.js +0 -579
- package/plugins/logger.js +0 -14
- package/plugins/redis.js +0 -32
- package/plugins/tool.js +0 -8
- package/scripts/syncDb.js +0 -752
- package/system.js +0 -118
- package/tables/common.json +0 -16
- package/tables/tool.json +0 -6
- package/utils/api.js +0 -27
- package/utils/crypto.js +0 -260
- package/utils/index.js +0 -334
- package/utils/jwt.js +0 -387
- package/utils/logger.js +0 -143
- package/utils/redisHelper.js +0 -74
- package/utils/sqlManager.js +0 -471
- package/utils/tool.js +0 -31
- package/utils/validate.js +0 -226
package/main.ts
ADDED
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Befly 框架主入口文件 - TypeScript 版本
|
|
3
|
+
* 提供完整的框架功能:检查系统、加载插件、加载 API、启动 HTTP 服务器
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Env } from './config/env.js';
|
|
7
|
+
import { Api } from './utils/api.js';
|
|
8
|
+
import { Yes, No } from './utils/index.js';
|
|
9
|
+
import { Logger } from './utils/logger.js';
|
|
10
|
+
import { Jwt } from './utils/jwt.js';
|
|
11
|
+
import { Validator } from './utils/validate.js';
|
|
12
|
+
import { Crypto2 } from './utils/crypto.js';
|
|
13
|
+
import { calcPerfTime } from './utils/index.js';
|
|
14
|
+
import { scanAddons, hasAddonDir } from './utils/addonHelper.js';
|
|
15
|
+
import { Checker } from './lifecycle/checker.js';
|
|
16
|
+
import { Loader } from './lifecycle/loader.js';
|
|
17
|
+
import { Bootstrap } from './lifecycle/bootstrap.js';
|
|
18
|
+
|
|
19
|
+
import type { Server } from 'bun';
|
|
20
|
+
import type { Plugin } from './types/plugin.js';
|
|
21
|
+
import type { ApiRoute, ApiHandler } from './types/api.js';
|
|
22
|
+
import type { BeflyContext, BeflyOptions, RequestContext } from './types/befly.js';
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Befly 框架核心类
|
|
26
|
+
*/
|
|
27
|
+
export class Befly {
|
|
28
|
+
/** API 路由映射表 */
|
|
29
|
+
private apiRoutes: Map<string, ApiRoute>;
|
|
30
|
+
|
|
31
|
+
/** 插件列表 */
|
|
32
|
+
private pluginLists: Plugin[];
|
|
33
|
+
|
|
34
|
+
/** 应用上下文 */
|
|
35
|
+
public appContext: BeflyContext;
|
|
36
|
+
|
|
37
|
+
/** 应用配置选项 */
|
|
38
|
+
private appOptions: BeflyOptions;
|
|
39
|
+
|
|
40
|
+
// 原构造函数被替换
|
|
41
|
+
constructor(options: BeflyOptions = {}) {
|
|
42
|
+
this.apiRoutes = new Map();
|
|
43
|
+
this.pluginLists = [];
|
|
44
|
+
this.appContext = {};
|
|
45
|
+
this.appOptions = options;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* 系统检查(已提取到 lifecycle/checker.ts)
|
|
50
|
+
*/
|
|
51
|
+
async initCheck(): Promise<void> {
|
|
52
|
+
await Checker.run();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* 加载插件(已提取到 lifecycle/loader.ts)
|
|
57
|
+
*/
|
|
58
|
+
async loadPlugins(): Promise<void> {
|
|
59
|
+
await Loader.loadPlugins(this);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* 加载API路由(已提取到 lifecycle/loader.ts)
|
|
64
|
+
*/
|
|
65
|
+
async loadApis(dirName: string, options?: { isAddon?: boolean; addonName?: string }): Promise<void> {
|
|
66
|
+
await Loader.loadApis(dirName, this.apiRoutes, options);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* 启动服务器
|
|
71
|
+
*/
|
|
72
|
+
async listen(callback?: (server: Server) => void): Promise<void> {
|
|
73
|
+
const serverStartTime = Bun.nanoseconds();
|
|
74
|
+
Logger.info('开始启动 Befly 服务器...');
|
|
75
|
+
|
|
76
|
+
// 执行启动前的检查和加载
|
|
77
|
+
await this.initCheck();
|
|
78
|
+
await this.loadPlugins();
|
|
79
|
+
await this.loadApis('core');
|
|
80
|
+
|
|
81
|
+
// 加载 addon APIs
|
|
82
|
+
const addons = scanAddons();
|
|
83
|
+
for (const addon of addons) {
|
|
84
|
+
if (hasAddonDir(addon, 'apis')) {
|
|
85
|
+
await this.loadApis(addon, { isAddon: true, addonName: addon });
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
await this.loadApis('app');
|
|
90
|
+
|
|
91
|
+
const totalStartupTime = calcPerfTime(serverStartTime);
|
|
92
|
+
Logger.info(`服务器启动准备完成,总耗时: ${totalStartupTime}`);
|
|
93
|
+
|
|
94
|
+
// 启动HTTP服务器(路由处理逻辑已提取到 router/*.ts)
|
|
95
|
+
await Bootstrap.start(this, callback);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// 核心类已通过 export class 导出
|
|
100
|
+
export { Env, Api, Jwt, Validator, Crypto2, Logger, Yes, No };
|
|
101
|
+
export type { ApiOptions, FieldRules } from './types/api.js';
|
package/package.json
CHANGED
|
@@ -1,19 +1,43 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "befly",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Befly - 为 Bun 专属打造的 API 接口框架核心引擎",
|
|
3
|
+
"version": "3.0.0",
|
|
4
|
+
"description": "Befly - 为 Bun 专属打造的 TypeScript API 接口框架核心引擎",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
7
7
|
"publishConfig": {
|
|
8
8
|
"access": "public",
|
|
9
9
|
"registry": "https://registry.npmjs.org"
|
|
10
10
|
},
|
|
11
|
-
"main": "main.
|
|
11
|
+
"main": "main.ts",
|
|
12
|
+
"types": "main.ts",
|
|
12
13
|
"exports": {
|
|
13
|
-
".":
|
|
14
|
+
".": {
|
|
15
|
+
"types": "./main.ts",
|
|
16
|
+
"default": "./main.ts"
|
|
17
|
+
},
|
|
18
|
+
"./types": {
|
|
19
|
+
"types": "./types/index.ts",
|
|
20
|
+
"default": "./types/index.ts"
|
|
21
|
+
},
|
|
22
|
+
"./types/*": {
|
|
23
|
+
"types": "./types/*.ts",
|
|
24
|
+
"default": "./types/*.ts"
|
|
25
|
+
},
|
|
26
|
+
"./utils": {
|
|
27
|
+
"types": "./utils/index.ts",
|
|
28
|
+
"default": "./utils/index.ts"
|
|
29
|
+
},
|
|
30
|
+
"./utils/*": {
|
|
31
|
+
"types": "./utils/*.ts",
|
|
32
|
+
"default": "./utils/*.ts"
|
|
33
|
+
},
|
|
34
|
+
"./config": {
|
|
35
|
+
"types": "./config/env.ts",
|
|
36
|
+
"default": "./config/env.ts"
|
|
37
|
+
}
|
|
14
38
|
},
|
|
15
39
|
"bin": {
|
|
16
|
-
"befly": "./bin/befly.
|
|
40
|
+
"befly": "./bin/befly.ts"
|
|
17
41
|
},
|
|
18
42
|
"scripts": {},
|
|
19
43
|
"keywords": [
|
|
@@ -21,10 +45,12 @@
|
|
|
21
45
|
"api",
|
|
22
46
|
"framework",
|
|
23
47
|
"core",
|
|
48
|
+
"typescript",
|
|
24
49
|
"javascript",
|
|
25
50
|
"backend",
|
|
26
51
|
"rest",
|
|
27
|
-
"http"
|
|
52
|
+
"http",
|
|
53
|
+
"type-safe"
|
|
28
54
|
],
|
|
29
55
|
"author": "chensuiyi <bimostyle@qq.com>",
|
|
30
56
|
"homepage": "https://chensuiyi.me",
|
|
@@ -37,6 +63,7 @@
|
|
|
37
63
|
"plugins/",
|
|
38
64
|
"scripts/",
|
|
39
65
|
"tables/",
|
|
66
|
+
"types/",
|
|
40
67
|
"utils/",
|
|
41
68
|
".gitignore",
|
|
42
69
|
".npmignore",
|
|
@@ -46,10 +73,19 @@
|
|
|
46
73
|
".prettierignore",
|
|
47
74
|
".prettierrc",
|
|
48
75
|
"bunfig.toml",
|
|
76
|
+
"tsconfig.json",
|
|
49
77
|
"LICENSE",
|
|
78
|
+
"main.ts",
|
|
50
79
|
"main.js",
|
|
80
|
+
"system.ts",
|
|
51
81
|
"system.js",
|
|
52
82
|
"package.json",
|
|
53
83
|
"README.md"
|
|
54
|
-
]
|
|
55
|
-
|
|
84
|
+
],
|
|
85
|
+
"engines": {
|
|
86
|
+
"bun": ">=1.3.0"
|
|
87
|
+
},
|
|
88
|
+
"devDependencies": {
|
|
89
|
+
"@types/bun": "latest"
|
|
90
|
+
}
|
|
91
|
+
}
|
package/plugins/{db.js → db.ts}
RENAMED
|
@@ -1,29 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 数据库插件 - TypeScript 版本
|
|
3
|
+
* 初始化数据库连接和 SQL 管理器
|
|
4
|
+
*/
|
|
5
|
+
|
|
1
6
|
import { Env } from '../config/env.js';
|
|
2
7
|
import { Logger } from '../utils/logger.js';
|
|
3
8
|
import { createSqlClient } from '../utils/index.js';
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
|
|
9
|
+
import { SqlHelper } from '../utils/sqlHelper.js';
|
|
10
|
+
import type { Plugin } from '../types/plugin.js';
|
|
11
|
+
import type { BeflyContext } from '../types/befly.js';
|
|
7
12
|
|
|
8
|
-
|
|
13
|
+
/**
|
|
14
|
+
* 数据库插件
|
|
15
|
+
*/
|
|
16
|
+
const dbPlugin: Plugin = {
|
|
17
|
+
name: '_db',
|
|
9
18
|
after: ['_redis'],
|
|
10
|
-
|
|
11
|
-
|
|
19
|
+
|
|
20
|
+
async onInit(befly: BeflyContext): Promise<SqlHelper | Record<string, never>> {
|
|
21
|
+
let sql: any = null;
|
|
12
22
|
|
|
13
23
|
try {
|
|
14
24
|
if (Env.DB_ENABLE === 1) {
|
|
15
25
|
// 创建 Bun SQL 客户端(内置连接池),并确保连接验证成功后再继续
|
|
16
26
|
sql = await createSqlClient();
|
|
17
27
|
|
|
18
|
-
//
|
|
19
|
-
const dbManager = new
|
|
28
|
+
// 创建数据库管理器实例,直接传入 sql 对象
|
|
29
|
+
const dbManager = new SqlHelper(befly, sql);
|
|
20
30
|
|
|
31
|
+
Logger.info('数据库插件初始化成功');
|
|
21
32
|
return dbManager;
|
|
22
33
|
} else {
|
|
23
|
-
Logger.warn(
|
|
34
|
+
Logger.warn('数据库未启用(DB_ENABLE≠1),跳过初始化');
|
|
24
35
|
return {};
|
|
25
36
|
}
|
|
26
|
-
} catch (error) {
|
|
37
|
+
} catch (error: any) {
|
|
27
38
|
Logger.error({
|
|
28
39
|
msg: '数据库初始化失败',
|
|
29
40
|
message: error.message,
|
|
@@ -34,7 +45,7 @@ export default {
|
|
|
34
45
|
if (sql) {
|
|
35
46
|
try {
|
|
36
47
|
await sql.close();
|
|
37
|
-
} catch (cleanupError) {
|
|
48
|
+
} catch (cleanupError: any) {
|
|
38
49
|
Logger.error('清理连接池失败:', cleanupError);
|
|
39
50
|
}
|
|
40
51
|
}
|
|
@@ -44,3 +55,5 @@ export default {
|
|
|
44
55
|
}
|
|
45
56
|
}
|
|
46
57
|
};
|
|
58
|
+
|
|
59
|
+
export default dbPlugin;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 日志插件 - TypeScript 版本
|
|
3
|
+
* 提供全局日志功能
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Logger } from '../utils/logger.js';
|
|
7
|
+
import type { Plugin } from '../types/plugin.js';
|
|
8
|
+
import type { BeflyContext } from '../types/befly.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 日志插件
|
|
12
|
+
*/
|
|
13
|
+
const loggerPlugin: Plugin = {
|
|
14
|
+
name: '_logger',
|
|
15
|
+
after: [],
|
|
16
|
+
|
|
17
|
+
async onInit(befly: BeflyContext): Promise<typeof Logger> {
|
|
18
|
+
try {
|
|
19
|
+
Logger.info('日志插件初始化成功');
|
|
20
|
+
return Logger;
|
|
21
|
+
} catch (error: any) {
|
|
22
|
+
// 插件内禁止直接退出进程,抛出异常交由主流程统一处理
|
|
23
|
+
throw error;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export default loggerPlugin;
|
package/plugins/redis.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Redis 插件 - TypeScript 版本
|
|
3
|
+
* 初始化 Redis 连接和助手工具
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Env } from '../config/env.js';
|
|
7
|
+
import { Logger } from '../utils/logger.js';
|
|
8
|
+
import { RedisHelper, initRedisClient, getRedisClient } from '../utils/redisHelper.js';
|
|
9
|
+
import type { Plugin } from '../types/plugin.js';
|
|
10
|
+
import type { BeflyContext } from '../types/befly.js';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Redis 插件
|
|
14
|
+
*/
|
|
15
|
+
const redisPlugin: Plugin = {
|
|
16
|
+
name: '_redis',
|
|
17
|
+
after: ['_logger'],
|
|
18
|
+
|
|
19
|
+
async onInit(befly: BeflyContext): Promise<typeof RedisHelper | Record<string, never>> {
|
|
20
|
+
try {
|
|
21
|
+
if (Env.REDIS_ENABLE === 1) {
|
|
22
|
+
// 初始化 Redis 客户端(带连接测试)
|
|
23
|
+
await initRedisClient();
|
|
24
|
+
|
|
25
|
+
Logger.info('Redis 插件初始化成功', {
|
|
26
|
+
host: Env.REDIS_HOST,
|
|
27
|
+
port: Env.REDIS_PORT,
|
|
28
|
+
db: Env.REDIS_DB
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
// 返回工具对象,向下游以相同 API 暴露
|
|
32
|
+
return RedisHelper;
|
|
33
|
+
} else {
|
|
34
|
+
Logger.warn('Redis 未启用,跳过初始化');
|
|
35
|
+
return {};
|
|
36
|
+
}
|
|
37
|
+
} catch (error: any) {
|
|
38
|
+
Logger.error({
|
|
39
|
+
msg: 'Redis 初始化失败',
|
|
40
|
+
message: error.message,
|
|
41
|
+
code: error.code,
|
|
42
|
+
stack: error.stack
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// 插件内禁止直接退出进程,抛出异常交由主流程统一处理
|
|
46
|
+
throw error;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
|
|
51
|
+
export default redisPlugin;
|
package/plugins/tool.ts
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tool 插件 - TypeScript 版本
|
|
3
|
+
* 提供数据处理工具
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Tool } from '../utils/tool.js';
|
|
7
|
+
import { Logger } from '../utils/logger.js';
|
|
8
|
+
import type { Plugin } from '../types/plugin.js';
|
|
9
|
+
import type { BeflyContext } from '../types/befly.js';
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Tool 插件
|
|
13
|
+
*/
|
|
14
|
+
const toolPlugin: Plugin = {
|
|
15
|
+
name: '_tool',
|
|
16
|
+
after: ['_redis', '_db'],
|
|
17
|
+
|
|
18
|
+
async onInit(befly: BeflyContext): Promise<Tool> {
|
|
19
|
+
try {
|
|
20
|
+
const tool = new Tool(befly);
|
|
21
|
+
Logger.info('Tool 插件初始化成功');
|
|
22
|
+
return tool;
|
|
23
|
+
} catch (error: any) {
|
|
24
|
+
Logger.error({
|
|
25
|
+
msg: 'Tool 初始化失败',
|
|
26
|
+
message: error.message,
|
|
27
|
+
stack: error.stack
|
|
28
|
+
});
|
|
29
|
+
throw error;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
export default toolPlugin;
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* syncDb 变更应用模块
|
|
3
|
+
*
|
|
4
|
+
* 包含:
|
|
5
|
+
* - 比较字段定义变化
|
|
6
|
+
* - 应用表结构变更计划
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { Logger } from '../../utils/logger.js';
|
|
10
|
+
import { parseRule } from '../../utils/tableHelper.js';
|
|
11
|
+
import { IS_MYSQL, IS_PG, IS_SQLITE, CHANGE_TYPE_LABELS, typeMapping } from './constants.js';
|
|
12
|
+
import { logFieldChange, resolveDefaultValue, isStringOrArrayType } from './helpers.js';
|
|
13
|
+
import { executeDDLSafely, buildIndexSQL } from './ddl.js';
|
|
14
|
+
import { rebuildSqliteTable } from './sqlite.js';
|
|
15
|
+
import type { FieldChange, IndexAction, TablePlan, ColumnInfo } from './types.js';
|
|
16
|
+
import type { SQL } from 'bun';
|
|
17
|
+
|
|
18
|
+
// 是否为计划模式(从环境变量读取)
|
|
19
|
+
const IS_PLAN = process.argv.includes('--plan');
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* 比较字段定义变化
|
|
23
|
+
*
|
|
24
|
+
* 对比现有列信息和新的字段规则,识别变化类型:
|
|
25
|
+
* - 长度变化(string/array 类型)
|
|
26
|
+
* - 注释变化(MySQL/PG)
|
|
27
|
+
* - 数据类型变化
|
|
28
|
+
* - 默认值变化
|
|
29
|
+
*
|
|
30
|
+
* @param existingColumn - 现有列信息
|
|
31
|
+
* @param newRule - 新的字段规则字符串
|
|
32
|
+
* @param colName - 列名(未使用,保留参数兼容性)
|
|
33
|
+
* @returns 变化数组
|
|
34
|
+
*/
|
|
35
|
+
export function compareFieldDefinition(existingColumn: ColumnInfo, newRule: string, colName: string): FieldChange[] {
|
|
36
|
+
const parsed = parseRule(newRule);
|
|
37
|
+
const { name: fieldName, type: fieldType, max: fieldMax, default: fieldDefault } = parsed;
|
|
38
|
+
const changes: FieldChange[] = [];
|
|
39
|
+
|
|
40
|
+
// 检查长度变化(string和array类型) - SQLite 不比较长度
|
|
41
|
+
if (!IS_SQLITE && isStringOrArrayType(fieldType)) {
|
|
42
|
+
if (existingColumn.length !== fieldMax) {
|
|
43
|
+
changes.push({
|
|
44
|
+
type: 'length',
|
|
45
|
+
current: existingColumn.length,
|
|
46
|
+
expected: fieldMax
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// 检查注释变化(MySQL/PG 支持列注释)
|
|
52
|
+
if (!IS_SQLITE) {
|
|
53
|
+
const currentComment = existingColumn.comment || '';
|
|
54
|
+
if (currentComment !== fieldName) {
|
|
55
|
+
changes.push({
|
|
56
|
+
type: 'comment',
|
|
57
|
+
current: currentComment,
|
|
58
|
+
expected: fieldName
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// 检查数据类型变化(按方言)
|
|
64
|
+
if (existingColumn.type.toLowerCase() !== typeMapping[fieldType].toLowerCase()) {
|
|
65
|
+
changes.push({
|
|
66
|
+
type: 'datatype',
|
|
67
|
+
current: existingColumn.type,
|
|
68
|
+
expected: typeMapping[fieldType].toLowerCase()
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// 使用公共函数处理默认值
|
|
73
|
+
const expectedDefault = resolveDefaultValue(fieldDefault, fieldType);
|
|
74
|
+
|
|
75
|
+
// 检查默认值变化
|
|
76
|
+
if (String(existingColumn.defaultValue) !== String(expectedDefault)) {
|
|
77
|
+
changes.push({
|
|
78
|
+
type: 'default',
|
|
79
|
+
current: existingColumn.defaultValue,
|
|
80
|
+
expected: expectedDefault
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
return changes;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* 将表结构计划应用到数据库(执行 DDL/索引/注释等)
|
|
89
|
+
*
|
|
90
|
+
* 根据数据库方言和计划内容,执行相应的 DDL 操作:
|
|
91
|
+
* - SQLite: 新增字段直接 ALTER,其他操作需要重建表
|
|
92
|
+
* - MySQL: 尝试在线 DDL(INSTANT/INPLACE)
|
|
93
|
+
* - PostgreSQL: 直接 ALTER
|
|
94
|
+
*
|
|
95
|
+
* @param sql - SQL 客户端实例
|
|
96
|
+
* @param tableName - 表名
|
|
97
|
+
* @param fields - 字段定义对象
|
|
98
|
+
* @param plan - 表结构变更计划
|
|
99
|
+
* @param globalCount - 全局统计对象(用于计数)
|
|
100
|
+
*/
|
|
101
|
+
export async function applyTablePlan(sql: SQL, tableName: string, fields: Record<string, string>, plan: TablePlan, globalCount: Record<string, number>): Promise<void> {
|
|
102
|
+
if (!plan || !plan.changed) return;
|
|
103
|
+
|
|
104
|
+
// SQLite: 仅支持部分 ALTER;需要时走重建
|
|
105
|
+
if (IS_SQLITE) {
|
|
106
|
+
if (plan.modifyClauses.length > 0 || plan.defaultClauses.length > 0) {
|
|
107
|
+
if (IS_PLAN) Logger.info(`[计划] 重建表 ${tableName} 以应用列修改/默认值变化`);
|
|
108
|
+
else await rebuildSqliteTable(sql, tableName, fields);
|
|
109
|
+
} else {
|
|
110
|
+
for (const c of plan.addClauses) {
|
|
111
|
+
const stmt = `ALTER TABLE "${tableName}" ${c}`;
|
|
112
|
+
if (IS_PLAN) Logger.info(`[计划] ${stmt}`);
|
|
113
|
+
else await sql.unsafe(stmt);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
} else {
|
|
117
|
+
const clauses = [...plan.addClauses, ...plan.modifyClauses];
|
|
118
|
+
if (clauses.length > 0) {
|
|
119
|
+
const suffix = IS_MYSQL ? ', ALGORITHM=INSTANT, LOCK=NONE' : '';
|
|
120
|
+
const stmt = IS_MYSQL ? `ALTER TABLE \`${tableName}\` ${clauses.join(', ')}${suffix}` : `ALTER TABLE "${tableName}" ${clauses.join(', ')}`;
|
|
121
|
+
if (IS_PLAN) Logger.info(`[计划] ${stmt}`);
|
|
122
|
+
else if (IS_MYSQL) await executeDDLSafely(sql, stmt);
|
|
123
|
+
else await sql.unsafe(stmt);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// 默认值专用 ALTER(SQLite 不支持)
|
|
128
|
+
if (plan.defaultClauses.length > 0) {
|
|
129
|
+
if (IS_SQLITE) {
|
|
130
|
+
Logger.warn(`SQLite 不支持修改默认值,表 ${tableName} 的默认值变更已跳过`);
|
|
131
|
+
} else {
|
|
132
|
+
const suffix = IS_MYSQL ? ', ALGORITHM=INSTANT, LOCK=NONE' : '';
|
|
133
|
+
const stmt = IS_MYSQL ? `ALTER TABLE \`${tableName}\` ${plan.defaultClauses.join(', ')}${suffix}` : `ALTER TABLE "${tableName}" ${plan.defaultClauses.join(', ')}`;
|
|
134
|
+
if (IS_PLAN) Logger.info(`[计划] ${stmt}`);
|
|
135
|
+
else if (IS_MYSQL) await executeDDLSafely(sql, stmt);
|
|
136
|
+
else await sql.unsafe(stmt);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// 索引操作
|
|
141
|
+
for (const act of plan.indexActions) {
|
|
142
|
+
const stmt = buildIndexSQL(tableName, act.indexName, act.fieldName, act.action);
|
|
143
|
+
if (IS_PLAN) {
|
|
144
|
+
Logger.info(`[计划] ${stmt}`);
|
|
145
|
+
} else {
|
|
146
|
+
try {
|
|
147
|
+
await sql.unsafe(stmt);
|
|
148
|
+
if (act.action === 'create') {
|
|
149
|
+
Logger.info(`[索引变化] 新建索引 ${tableName}.${act.indexName} (${act.fieldName})`);
|
|
150
|
+
} else {
|
|
151
|
+
Logger.info(`[索引变化] 删除索引 ${tableName}.${act.indexName} (${act.fieldName})`);
|
|
152
|
+
}
|
|
153
|
+
} catch (error: any) {
|
|
154
|
+
Logger.error(`${act.action === 'create' ? '创建' : '删除'}索引失败: ${error.message}`);
|
|
155
|
+
Logger.error(`表名: ${tableName}, 索引名: ${act.indexName}, 字段: ${act.fieldName}`);
|
|
156
|
+
throw error;
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// PG 列注释
|
|
162
|
+
if (IS_PG && plan.commentActions && plan.commentActions.length > 0) {
|
|
163
|
+
for (const stmt of plan.commentActions) {
|
|
164
|
+
if (IS_PLAN) Logger.info(`[计划] ${stmt}`);
|
|
165
|
+
else await sql.unsafe(stmt);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// 计数
|
|
170
|
+
globalCount.modifiedTables++;
|
|
171
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* syncDb 常量定义模块
|
|
3
|
+
*
|
|
4
|
+
* 包含:
|
|
5
|
+
* - 数据库版本要求
|
|
6
|
+
* - 系统字段定义
|
|
7
|
+
* - 索引字段配置
|
|
8
|
+
* - 字段变更类型标签
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { Env } from '../../config/env.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 数据库版本要求
|
|
15
|
+
*/
|
|
16
|
+
export const DB_VERSION_REQUIREMENTS = {
|
|
17
|
+
MYSQL_MIN_MAJOR: 8,
|
|
18
|
+
POSTGRES_MIN_MAJOR: 17,
|
|
19
|
+
SQLITE_MIN_VERSION: '3.50.0',
|
|
20
|
+
SQLITE_MIN_VERSION_NUM: 35000 // 3 * 10000 + 50 * 100
|
|
21
|
+
} as const;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 系统字段定义(所有表都包含的固定字段)
|
|
25
|
+
*/
|
|
26
|
+
export const SYSTEM_FIELDS = {
|
|
27
|
+
ID: { name: 'id', comment: '主键ID' },
|
|
28
|
+
CREATED_AT: { name: 'created_at', comment: '创建时间' },
|
|
29
|
+
UPDATED_AT: { name: 'updated_at', comment: '更新时间' },
|
|
30
|
+
DELETED_AT: { name: 'deleted_at', comment: '删除时间' },
|
|
31
|
+
STATE: { name: 'state', comment: '状态字段' }
|
|
32
|
+
} as const;
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* 需要创建索引的系统字段
|
|
36
|
+
*/
|
|
37
|
+
export const SYSTEM_INDEX_FIELDS = ['created_at', 'updated_at', 'state'] as const;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* 字段变更类型的中文标签映射
|
|
41
|
+
*/
|
|
42
|
+
export const CHANGE_TYPE_LABELS = {
|
|
43
|
+
length: '长度',
|
|
44
|
+
datatype: '类型',
|
|
45
|
+
comment: '注释',
|
|
46
|
+
default: '默认值'
|
|
47
|
+
} as const;
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* MySQL 表配置(支持环境变量自定义)
|
|
51
|
+
*/
|
|
52
|
+
export const MYSQL_TABLE_CONFIG = {
|
|
53
|
+
ENGINE: Env.MYSQL_ENGINE || 'InnoDB',
|
|
54
|
+
CHARSET: Env.MYSQL_CHARSET || 'utf8mb4',
|
|
55
|
+
COLLATE: Env.MYSQL_COLLATE || 'utf8mb4_0900_as_cs'
|
|
56
|
+
} as const;
|
|
57
|
+
|
|
58
|
+
// 数据库类型判断
|
|
59
|
+
export const DB = (Env.DB_TYPE || 'mysql').toLowerCase();
|
|
60
|
+
export const IS_MYSQL = DB === 'mysql';
|
|
61
|
+
export const IS_PG = DB === 'postgresql' || DB === 'postgres';
|
|
62
|
+
export const IS_SQLITE = DB === 'sqlite';
|
|
63
|
+
|
|
64
|
+
// 字段类型映射(按方言)
|
|
65
|
+
export const typeMapping = {
|
|
66
|
+
number: IS_SQLITE ? 'INTEGER' : 'BIGINT',
|
|
67
|
+
string: IS_SQLITE ? 'TEXT' : IS_PG ? 'character varying' : 'VARCHAR',
|
|
68
|
+
text: IS_MYSQL ? 'MEDIUMTEXT' : 'TEXT',
|
|
69
|
+
array: IS_SQLITE ? 'TEXT' : IS_PG ? 'character varying' : 'VARCHAR'
|
|
70
|
+
};
|