befly 3.8.25 → 3.8.27
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/config.ts +8 -9
- package/hooks/{rateLimit.ts → _rateLimit.ts} +7 -13
- package/hooks/auth.ts +3 -11
- package/hooks/cors.ts +1 -4
- package/hooks/parser.ts +6 -8
- package/hooks/permission.ts +9 -12
- package/hooks/validator.ts +6 -9
- package/lib/cacheHelper.ts +0 -4
- package/lib/{database.ts → connect.ts} +65 -18
- package/lib/logger.ts +1 -17
- package/lib/redisHelper.ts +6 -5
- package/loader/loadApis.ts +3 -3
- package/loader/loadHooks.ts +15 -41
- package/loader/loadPlugins.ts +10 -16
- package/main.ts +25 -28
- package/package.json +3 -3
- package/plugins/cache.ts +2 -2
- package/plugins/cipher.ts +15 -0
- package/plugins/config.ts +16 -0
- package/plugins/db.ts +7 -17
- package/plugins/jwt.ts +15 -0
- package/plugins/logger.ts +1 -1
- package/plugins/redis.ts +4 -4
- package/plugins/tool.ts +50 -0
- package/router/api.ts +56 -42
- package/router/static.ts +12 -12
- package/sync/syncAll.ts +2 -20
- package/sync/syncApi.ts +7 -7
- package/sync/syncDb/apply.ts +1 -4
- package/sync/syncDb/constants.ts +3 -0
- package/sync/syncDb/ddl.ts +2 -1
- package/sync/syncDb/helpers.ts +5 -117
- package/sync/syncDb/sqlite.ts +1 -3
- package/sync/syncDb/table.ts +8 -142
- package/sync/syncDb/tableCreate.ts +25 -9
- package/sync/syncDb/types.ts +125 -0
- package/sync/syncDb/version.ts +0 -3
- package/sync/syncDb.ts +146 -6
- package/sync/syncDev.ts +19 -15
- package/sync/syncMenu.ts +87 -75
- package/tests/redisHelper.test.ts +15 -16
- package/tests/sync-connection.test.ts +189 -0
- package/tests/syncDb-apply.test.ts +287 -0
- package/tests/syncDb-constants.test.ts +150 -0
- package/tests/syncDb-ddl.test.ts +205 -0
- package/tests/syncDb-helpers.test.ts +112 -0
- package/tests/syncDb-schema.test.ts +178 -0
- package/tests/syncDb-types.test.ts +129 -0
- package/tsconfig.json +2 -2
- package/types/api.d.ts +1 -1
- package/types/befly.d.ts +23 -21
- package/types/common.d.ts +0 -29
- package/types/context.d.ts +8 -6
- package/types/hook.d.ts +3 -4
- package/types/plugin.d.ts +3 -0
- package/hooks/errorHandler.ts +0 -23
- package/hooks/requestId.ts +0 -24
- package/hooks/requestLogger.ts +0 -25
- package/hooks/responseFormatter.ts +0 -64
- package/router/root.ts +0 -56
- package/sync/syncDb/index.ts +0 -164
package/types/befly.d.ts
CHANGED
|
@@ -146,32 +146,34 @@ export interface BeflyOptions {
|
|
|
146
146
|
* 包含所有插件挂载的实例
|
|
147
147
|
*/
|
|
148
148
|
export interface BeflyContext extends KeyValue {
|
|
149
|
-
// ==========
|
|
150
|
-
/** 数据库助手
|
|
151
|
-
_db?: DbHelper;
|
|
152
|
-
|
|
153
|
-
/** Redis 助手 (redis 插件) */
|
|
154
|
-
_redis?: RedisHelper;
|
|
155
|
-
|
|
156
|
-
/** 日志器 (logger 插件) */
|
|
157
|
-
_logger?: typeof Logger;
|
|
158
|
-
|
|
159
|
-
/** 缓存助手 (cache 插件) */
|
|
160
|
-
_cache?: CacheHelper;
|
|
161
|
-
|
|
162
|
-
// ========== 核心插件便捷访问(无前缀) ==========
|
|
163
|
-
/** 数据库助手便捷访问 */
|
|
149
|
+
// ========== 核心插件 ==========
|
|
150
|
+
/** 数据库助手 */
|
|
164
151
|
db?: DbHelper;
|
|
165
152
|
|
|
166
|
-
/** Redis
|
|
153
|
+
/** Redis 助手 */
|
|
167
154
|
redis?: RedisHelper;
|
|
168
155
|
|
|
169
|
-
/**
|
|
156
|
+
/** 日志器 */
|
|
170
157
|
logger?: typeof Logger;
|
|
171
158
|
|
|
172
|
-
/**
|
|
159
|
+
/** 缓存助手 */
|
|
173
160
|
cache?: CacheHelper;
|
|
174
161
|
|
|
162
|
+
/** 工具函数 */
|
|
163
|
+
tool?: {
|
|
164
|
+
Yes: (msg: string, data?: any, other?: Record<string, any>) => { code: 0; msg: string; data: any };
|
|
165
|
+
No: (msg: string, data?: any, other?: Record<string, any>) => { code: 1; msg: string; data: any };
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
/** 加密解密 */
|
|
169
|
+
cipher?: typeof Cipher;
|
|
170
|
+
|
|
171
|
+
/** JWT 令牌 */
|
|
172
|
+
jwt?: typeof Jwt;
|
|
173
|
+
|
|
174
|
+
/** 项目配置 */
|
|
175
|
+
config?: BeflyOptions;
|
|
176
|
+
|
|
175
177
|
// ========== 动态插件 ==========
|
|
176
178
|
/** 组件插件:addon_{addonName}_{pluginName} */
|
|
177
179
|
/** 项目插件:app_{pluginName} */
|
|
@@ -183,13 +185,13 @@ export interface BeflyContext extends KeyValue {
|
|
|
183
185
|
*/
|
|
184
186
|
export interface Befly {
|
|
185
187
|
/** API 路由映射 */
|
|
186
|
-
|
|
188
|
+
apis: Map<string, ApiRoute>;
|
|
187
189
|
|
|
188
190
|
/** 插件列表 */
|
|
189
|
-
|
|
191
|
+
plugins: Plugin[];
|
|
190
192
|
|
|
191
193
|
/** 应用上下文 */
|
|
192
|
-
|
|
194
|
+
context: KeyValue;
|
|
193
195
|
|
|
194
196
|
/** 应用选项 */
|
|
195
197
|
appOptions: BeflyOptions;
|
package/types/common.d.ts
CHANGED
|
@@ -128,35 +128,6 @@ export interface LoggerConfig {
|
|
|
128
128
|
};
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
-
/**
|
|
132
|
-
* 环境变量类型
|
|
133
|
-
*/
|
|
134
|
-
export interface EnvConfig {
|
|
135
|
-
// 服务配置
|
|
136
|
-
appName: string;
|
|
137
|
-
appHost: string;
|
|
138
|
-
appPort: number;
|
|
139
|
-
|
|
140
|
-
// 数据库配置
|
|
141
|
-
mysqlHost: string;
|
|
142
|
-
mysqlPort: number;
|
|
143
|
-
mysqlUsername: string;
|
|
144
|
-
mysqlPassword: string;
|
|
145
|
-
mysqlDatabase: string;
|
|
146
|
-
|
|
147
|
-
// Redis 配置
|
|
148
|
-
redisHost?: string;
|
|
149
|
-
redisPort?: number;
|
|
150
|
-
redisPassword?: string;
|
|
151
|
-
|
|
152
|
-
// JWT 配置
|
|
153
|
-
jwtSecret: string;
|
|
154
|
-
jwtExpires?: string;
|
|
155
|
-
|
|
156
|
-
// 其他配置
|
|
157
|
-
[key: string]: any;
|
|
158
|
-
}
|
|
159
|
-
|
|
160
131
|
/**
|
|
161
132
|
* 工具函数返回类型
|
|
162
133
|
*/
|
package/types/context.d.ts
CHANGED
|
@@ -17,17 +17,19 @@ export interface RequestContext {
|
|
|
17
17
|
/** 请求开始时间(毫秒) */
|
|
18
18
|
now: number;
|
|
19
19
|
/** 客户端 IP 地址 */
|
|
20
|
-
ip
|
|
20
|
+
ip: string;
|
|
21
|
+
/** 请求头 */
|
|
22
|
+
headers: Headers;
|
|
21
23
|
/** API 路由路径(如 POST/api/user/login) */
|
|
22
|
-
route
|
|
24
|
+
route: string;
|
|
25
|
+
/** 请求唯一 ID */
|
|
26
|
+
requestId: string;
|
|
27
|
+
/** CORS 响应头 */
|
|
28
|
+
corsHeaders: Record<string, string>;
|
|
23
29
|
/** 当前请求的 API 路由对象 */
|
|
24
30
|
api?: ApiRoute;
|
|
25
31
|
/** 响应对象(如果设置了此属性,将直接返回该响应) */
|
|
26
32
|
response?: Response;
|
|
27
33
|
/** 原始处理结果(未转换为 Response 对象前) */
|
|
28
34
|
result?: any;
|
|
29
|
-
/** 请求唯一 ID */
|
|
30
|
-
requestId?: string;
|
|
31
|
-
/** CORS 响应头 */
|
|
32
|
-
corsHeaders?: Record<string, string>;
|
|
33
35
|
}
|
package/types/hook.d.ts
CHANGED
|
@@ -6,10 +6,9 @@ import type { BeflyContext } from './befly.js';
|
|
|
6
6
|
import type { RequestContext } from './context.js';
|
|
7
7
|
|
|
8
8
|
/**
|
|
9
|
-
*
|
|
9
|
+
* 钩子处理函数类型(串联模式,无 next 参数)
|
|
10
10
|
*/
|
|
11
|
-
export type
|
|
12
|
-
export type HookHandler = (befly: BeflyContext, ctx: RequestContext, next: Next) => Promise<void> | void;
|
|
11
|
+
export type HookHandler = (befly: BeflyContext, ctx: RequestContext) => Promise<void> | void;
|
|
13
12
|
|
|
14
13
|
/**
|
|
15
14
|
* 钩子配置类型
|
|
@@ -21,7 +20,7 @@ export interface Hook {
|
|
|
21
20
|
/** 依赖的钩子列表(在这些钩子之后执行) */
|
|
22
21
|
after?: string[];
|
|
23
22
|
|
|
24
|
-
/**
|
|
23
|
+
/** 执行顺序(数字越小越先执行) */
|
|
25
24
|
order?: number;
|
|
26
25
|
|
|
27
26
|
/** 钩子处理函数 */
|
package/types/plugin.d.ts
CHANGED
package/hooks/errorHandler.ts
DELETED
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
// 相对导入
|
|
2
|
-
import { Logger } from '../lib/logger.js';
|
|
3
|
-
import { JsonResponse } from '../util.js';
|
|
4
|
-
|
|
5
|
-
// 类型导入
|
|
6
|
-
import type { Hook } from '../types/hook.js';
|
|
7
|
-
|
|
8
|
-
const hook: Hook = {
|
|
9
|
-
order: 1,
|
|
10
|
-
handler: async (befly, ctx, next) => {
|
|
11
|
-
try {
|
|
12
|
-
await next();
|
|
13
|
-
} catch (err: any) {
|
|
14
|
-
// 记录错误信息
|
|
15
|
-
const apiPath = ctx.api ? `${ctx.req.method}${new URL(ctx.req.url).pathname}` : ctx.req.url;
|
|
16
|
-
Logger.error(`Request Error: ${apiPath}`, err);
|
|
17
|
-
|
|
18
|
-
// 设置错误响应
|
|
19
|
-
ctx.response = JsonResponse(ctx, '内部服务错误');
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
};
|
|
23
|
-
export default hook;
|
package/hooks/requestId.ts
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
import type { Hook } from '../types/hook.js';
|
|
2
|
-
import { logContextStorage } from '../lib/logger.js';
|
|
3
|
-
|
|
4
|
-
const hook: Hook = {
|
|
5
|
-
after: ['errorHandler'],
|
|
6
|
-
order: 3,
|
|
7
|
-
handler: async (befly, ctx, next) => {
|
|
8
|
-
// 生成唯一请求 ID
|
|
9
|
-
const requestId = crypto.randomUUID();
|
|
10
|
-
ctx.requestId = requestId;
|
|
11
|
-
|
|
12
|
-
// 添加到 CORS 响应头
|
|
13
|
-
if (!ctx.corsHeaders) {
|
|
14
|
-
ctx.corsHeaders = {};
|
|
15
|
-
}
|
|
16
|
-
ctx.corsHeaders['X-Request-ID'] = requestId;
|
|
17
|
-
|
|
18
|
-
// 在 AsyncLocalStorage 上下文中执行后续钩子
|
|
19
|
-
await logContextStorage.run({ requestId }, async () => {
|
|
20
|
-
await next();
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
};
|
|
24
|
-
export default hook;
|
package/hooks/requestLogger.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
// 相对导入
|
|
2
|
-
import { Logger } from '../lib/logger.js';
|
|
3
|
-
|
|
4
|
-
// 类型导入
|
|
5
|
-
import type { Hook } from '../types/hook.js';
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* 请求日志记录钩子
|
|
9
|
-
* 记录请求方法、路径、用户信息和响应时间
|
|
10
|
-
*/
|
|
11
|
-
const hook: Hook = {
|
|
12
|
-
after: ['parser'],
|
|
13
|
-
order: 30,
|
|
14
|
-
handler: async (befly, ctx, next) => {
|
|
15
|
-
await next();
|
|
16
|
-
|
|
17
|
-
if (ctx.api) {
|
|
18
|
-
const apiPath = `${ctx.req.method}${new URL(ctx.req.url).pathname}`;
|
|
19
|
-
const duration = Date.now() - ctx.now;
|
|
20
|
-
const user = ctx.user?.userId ? `[User:${ctx.user.userId}]` : '[Guest]';
|
|
21
|
-
Logger.info(`[${ctx.req.method}] ${apiPath} ${user} ${duration}ms`);
|
|
22
|
-
}
|
|
23
|
-
}
|
|
24
|
-
};
|
|
25
|
-
export default hook;
|
|
@@ -1,64 +0,0 @@
|
|
|
1
|
-
// 相对导入
|
|
2
|
-
import { JsonResponse } from '../util.js';
|
|
3
|
-
|
|
4
|
-
// 类型导入
|
|
5
|
-
import type { Hook } from '../types/hook.js';
|
|
6
|
-
|
|
7
|
-
const hook: Hook = {
|
|
8
|
-
after: ['requestId'],
|
|
9
|
-
order: 100,
|
|
10
|
-
handler: async (befly, ctx, next) => {
|
|
11
|
-
await next();
|
|
12
|
-
|
|
13
|
-
// 如果已经有 response,直接返回
|
|
14
|
-
if (ctx.response) {
|
|
15
|
-
return;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
// 如果有 result,格式化为 JSON 响应
|
|
19
|
-
if (ctx.result !== undefined) {
|
|
20
|
-
let result = ctx.result;
|
|
21
|
-
|
|
22
|
-
// 1. 如果是字符串,自动包裹为成功响应
|
|
23
|
-
if (typeof result === 'string') {
|
|
24
|
-
result = {
|
|
25
|
-
code: 0,
|
|
26
|
-
msg: result,
|
|
27
|
-
data: {}
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
// 2. 如果是对象,自动补充 code: 0
|
|
31
|
-
else if (result && typeof result === 'object') {
|
|
32
|
-
if (!('code' in result)) {
|
|
33
|
-
result = {
|
|
34
|
-
code: 0,
|
|
35
|
-
...result
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
// 处理 BigInt 序列化问题
|
|
41
|
-
if (result && typeof result === 'object') {
|
|
42
|
-
const jsonString = JSON.stringify(result, (key, value) => (typeof value === 'bigint' ? value.toString() : value));
|
|
43
|
-
ctx.response = new Response(jsonString, {
|
|
44
|
-
headers: {
|
|
45
|
-
...ctx.corsHeaders,
|
|
46
|
-
'Content-Type': 'application/json'
|
|
47
|
-
}
|
|
48
|
-
});
|
|
49
|
-
} else {
|
|
50
|
-
// 简单类型直接返回
|
|
51
|
-
ctx.response = Response.json(result, {
|
|
52
|
-
headers: ctx.corsHeaders
|
|
53
|
-
});
|
|
54
|
-
}
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// 如果还没有响应,且不是 OPTIONS 请求,则设置默认 JSON 响应
|
|
59
|
-
if (ctx.req.method !== 'OPTIONS' && !ctx.response) {
|
|
60
|
-
ctx.response = JsonResponse(ctx, 'No response generated');
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
};
|
|
64
|
-
export default hook;
|
package/router/root.ts
DELETED
|
@@ -1,56 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* 根路径路由处理器
|
|
3
|
-
* 处理 / 路径的请求
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
// 相对导入
|
|
7
|
-
import { Logger } from '../lib/logger.js';
|
|
8
|
-
import { setCorsOptions } from '../util.js';
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* 根路径处理器
|
|
12
|
-
*/
|
|
13
|
-
export async function rootHandler(req: Request): Promise<Response> {
|
|
14
|
-
// 设置 CORS 响应头
|
|
15
|
-
const corsHeaders = setCorsOptions(req);
|
|
16
|
-
|
|
17
|
-
try {
|
|
18
|
-
return Response.json(
|
|
19
|
-
{
|
|
20
|
-
code: 0,
|
|
21
|
-
msg: `Befly 接口服务已启动`,
|
|
22
|
-
data: {
|
|
23
|
-
mode: process.env.NODE_ENV || 'development',
|
|
24
|
-
timestamp: Date.now()
|
|
25
|
-
}
|
|
26
|
-
},
|
|
27
|
-
{
|
|
28
|
-
headers: corsHeaders
|
|
29
|
-
}
|
|
30
|
-
);
|
|
31
|
-
} catch (error: any) {
|
|
32
|
-
// 记录详细的错误日志
|
|
33
|
-
Logger.error('根路径处理失败', error);
|
|
34
|
-
|
|
35
|
-
// 根据错误类型返回不同的错误信息
|
|
36
|
-
let errorMessage = '服务异常';
|
|
37
|
-
let errorDetail = {};
|
|
38
|
-
|
|
39
|
-
// 开发环境返回详细错误信息
|
|
40
|
-
if (process.env.NODE_ENV === 'development') {
|
|
41
|
-
errorDetail = {
|
|
42
|
-
type: error.constructor?.name || 'Error',
|
|
43
|
-
message: error.message,
|
|
44
|
-
stack: error.stack
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
return Response.json(
|
|
49
|
-
{ code: 1, msg: errorMessage, data: errorDetail },
|
|
50
|
-
{
|
|
51
|
-
status: 500,
|
|
52
|
-
headers: corsHeaders
|
|
53
|
-
}
|
|
54
|
-
);
|
|
55
|
-
}
|
|
56
|
-
}
|
package/sync/syncDb/index.ts
DELETED
|
@@ -1,164 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* syncDb 主入口文件
|
|
3
|
-
*
|
|
4
|
-
* 功能:
|
|
5
|
-
* - 协调所有模块,执行数据库表结构同步
|
|
6
|
-
* - 处理核心表、项目表、addon 表
|
|
7
|
-
* - 提供统计信息和错误处理
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { basename, resolve } from 'pathe';
|
|
11
|
-
import { existsSync } from 'node:fs';
|
|
12
|
-
import { snakeCase } from 'es-toolkit/string';
|
|
13
|
-
import { Database } from '../../lib/database.js';
|
|
14
|
-
import { RedisHelper } from '../../lib/redisHelper.js';
|
|
15
|
-
import { checkTable } from '../../checks/checkTable.js';
|
|
16
|
-
import { scanFiles, scanAddons, addonDirExists, getAddonDir } from 'befly-util';
|
|
17
|
-
import { Logger } from '../../lib/logger.js';
|
|
18
|
-
import { projectDir } from '../../paths.js';
|
|
19
|
-
|
|
20
|
-
// 导入模块化的功能
|
|
21
|
-
import { ensureDbVersion } from './version.js';
|
|
22
|
-
import { tableExists } from './schema.js';
|
|
23
|
-
import { modifyTable } from './table.js';
|
|
24
|
-
import { createTable } from './tableCreate.js';
|
|
25
|
-
import { applyFieldDefaults } from './helpers.js';
|
|
26
|
-
import type { SQL } from 'bun';
|
|
27
|
-
import type { BeflyOptions, SyncDbOptions } from '../../types/index.js';
|
|
28
|
-
|
|
29
|
-
// 全局 SQL 客户端实例
|
|
30
|
-
let sql: SQL | null = null;
|
|
31
|
-
|
|
32
|
-
// 记录处理过的表名(用于清理缓存)
|
|
33
|
-
const processedTables: string[] = [];
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* 主同步函数
|
|
37
|
-
*
|
|
38
|
-
* 流程:
|
|
39
|
-
* 1. 验证表定义文件
|
|
40
|
-
* 2. 建立数据库连接并检查版本
|
|
41
|
-
* 3. 扫描表定义文件(核心表、项目表、addon表)
|
|
42
|
-
* 4. 对比并应用表结构变更
|
|
43
|
-
*/
|
|
44
|
-
export const SyncDb = async (config: BeflyOptions, options: SyncDbOptions = {}): Promise<void> => {
|
|
45
|
-
try {
|
|
46
|
-
// 清空处理记录
|
|
47
|
-
processedTables.length = 0;
|
|
48
|
-
|
|
49
|
-
// 验证表定义文件
|
|
50
|
-
await checkTable();
|
|
51
|
-
|
|
52
|
-
// 建立数据库连接并检查版本
|
|
53
|
-
sql = await Database.connectSql({ max: 1 });
|
|
54
|
-
await ensureDbVersion(sql);
|
|
55
|
-
|
|
56
|
-
// 初始化 Redis 连接(用于清理缓存)
|
|
57
|
-
await Database.connectRedis();
|
|
58
|
-
|
|
59
|
-
// 扫描表定义文件
|
|
60
|
-
const directories: Array<{ path: string; type: 'app' | 'addon'; addonName?: string; addonNameSnake?: string }> = [];
|
|
61
|
-
|
|
62
|
-
// 1. 项目表(无前缀)- 如果 tables 目录存在
|
|
63
|
-
const projectTablesDir = resolve(projectDir, 'tables');
|
|
64
|
-
if (existsSync(projectTablesDir)) {
|
|
65
|
-
directories.push({ path: projectTablesDir, type: 'app' });
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// 添加所有 addon 的 tables 目录(addon_{name}_ 前缀)
|
|
69
|
-
const addons = scanAddons();
|
|
70
|
-
for (const addon of addons) {
|
|
71
|
-
if (addonDirExists(addon, 'tables')) {
|
|
72
|
-
directories.push({
|
|
73
|
-
path: getAddonDir(addon, 'tables'),
|
|
74
|
-
type: 'addon',
|
|
75
|
-
addonName: addon,
|
|
76
|
-
addonNameSnake: snakeCase(addon) // 提前转换,避免每个文件都转换
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// 处理表文件
|
|
82
|
-
for (const dirConfig of directories) {
|
|
83
|
-
const { path: dir, type, addonName } = dirConfig;
|
|
84
|
-
const dirType = type === 'addon' ? `组件${addonName}` : '项目';
|
|
85
|
-
|
|
86
|
-
const files = await scanFiles(dir, '*.json');
|
|
87
|
-
|
|
88
|
-
for (const { filePath: file, fileName } of files) {
|
|
89
|
-
// 确定表名:
|
|
90
|
-
// - addon 表:{addonName}_{表名}
|
|
91
|
-
// 例如:admin addon 的 user.json → admin_user
|
|
92
|
-
// - 项目表:{表名}
|
|
93
|
-
// 例如:user.json → user
|
|
94
|
-
let tableName = snakeCase(fileName);
|
|
95
|
-
if (type === 'addon' && dirConfig.addonNameSnake) {
|
|
96
|
-
// addon 表,使用提前转换好的名称
|
|
97
|
-
tableName = `addon_${dirConfig.addonNameSnake}_${tableName}`;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
// 如果指定了表名,则只同步该表
|
|
101
|
-
if (options.table && options.table !== tableName) {
|
|
102
|
-
continue;
|
|
103
|
-
}
|
|
104
|
-
|
|
105
|
-
const tableDefinitionModule = await import(file, { with: { type: 'json' } });
|
|
106
|
-
const tableDefinition = tableDefinitionModule.default;
|
|
107
|
-
|
|
108
|
-
// 为字段属性设置默认值
|
|
109
|
-
for (const fieldDef of Object.values(tableDefinition)) {
|
|
110
|
-
applyFieldDefaults(fieldDef);
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
const dbName = config.plugins?.db?.database;
|
|
114
|
-
const existsTable = await tableExists(sql!, tableName, dbName);
|
|
115
|
-
|
|
116
|
-
// 读取 force 参数
|
|
117
|
-
const force = options.force || false;
|
|
118
|
-
|
|
119
|
-
if (existsTable) {
|
|
120
|
-
await modifyTable(sql!, tableName, tableDefinition, force, dbName);
|
|
121
|
-
} else {
|
|
122
|
-
await createTable(sql!, tableName, tableDefinition);
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
// 记录处理过的表名(用于清理缓存)
|
|
126
|
-
processedTables.push(tableName);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
// 清理 Redis 缓存(如果有表被处理)
|
|
131
|
-
if (processedTables.length > 0) {
|
|
132
|
-
Logger.debug(`🧹 清理 ${processedTables.length} 个表的字段缓存...`);
|
|
133
|
-
|
|
134
|
-
const redisHelper = new RedisHelper();
|
|
135
|
-
for (const tableName of processedTables) {
|
|
136
|
-
const cacheKey = `table:columns:${tableName}`;
|
|
137
|
-
try {
|
|
138
|
-
await redisHelper.del(cacheKey);
|
|
139
|
-
} catch (error: any) {
|
|
140
|
-
Logger.warn(`清理表 ${tableName} 的缓存失败: ${error.message}`);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
Logger.debug(`✓ 已清理表字段缓存`);
|
|
145
|
-
}
|
|
146
|
-
} catch (error: any) {
|
|
147
|
-
Logger.error(`数据库同步失败`, error);
|
|
148
|
-
throw error;
|
|
149
|
-
} finally {
|
|
150
|
-
if (sql) {
|
|
151
|
-
try {
|
|
152
|
-
await Database.disconnectSql();
|
|
153
|
-
} catch (error: any) {
|
|
154
|
-
Logger.warn(`关闭数据库连接时出错: ${error.message}`);
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
try {
|
|
159
|
-
await Database.disconnectRedis();
|
|
160
|
-
} catch (error: any) {
|
|
161
|
-
Logger.warn(`关闭 Redis 连接时出错: ${error.message}`);
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
};
|