befly 3.8.29 → 3.8.31
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 +91 -6
- package/checks/checkApi.ts +2 -1
- package/checks/checkApp.ts +31 -1
- package/checks/checkTable.ts +3 -2
- package/hooks/cors.ts +3 -3
- package/hooks/parser.ts +8 -6
- package/hooks/permission.ts +12 -5
- package/hooks/validator.ts +1 -1
- package/lib/cacheHelper.ts +73 -65
- package/lib/cipher.ts +2 -1
- package/lib/connect.ts +23 -52
- package/lib/dbHelper.ts +14 -11
- package/lib/jwt.ts +58 -437
- package/lib/logger.ts +76 -197
- package/lib/redisHelper.ts +163 -1
- package/lib/sqlBuilder.ts +2 -1
- package/lib/validator.ts +150 -384
- package/loader/loadApis.ts +4 -7
- package/loader/loadHooks.ts +6 -5
- package/loader/loadPlugins.ts +11 -13
- package/main.ts +26 -53
- package/package.json +10 -8
- package/paths.ts +0 -6
- package/plugins/cipher.ts +1 -1
- package/plugins/config.ts +3 -4
- package/plugins/db.ts +6 -7
- package/plugins/jwt.ts +7 -6
- package/plugins/logger.ts +6 -6
- package/plugins/redis.ts +9 -13
- package/router/api.ts +2 -2
- package/router/static.ts +4 -8
- package/sync/syncAll.ts +8 -13
- package/sync/syncApi.ts +14 -10
- package/sync/syncDb/apply.ts +1 -2
- package/sync/syncDb.ts +12 -15
- package/sync/syncDev.ts +19 -56
- package/sync/syncMenu.ts +182 -137
- package/tests/cacheHelper.test.ts +327 -0
- package/tests/dbHelper-columns.test.ts +5 -20
- package/tests/dbHelper-execute.test.ts +14 -68
- package/tests/fields-redis-cache.test.ts +5 -3
- package/tests/integration.test.ts +17 -32
- package/tests/jwt.test.ts +36 -94
- package/tests/logger.test.ts +32 -34
- package/tests/redisHelper.test.ts +271 -2
- package/tests/redisKeys.test.ts +76 -0
- package/tests/sync-connection.test.ts +0 -6
- package/tests/syncDb-constants.test.ts +12 -12
- package/tests/util.test.ts +5 -1
- package/tests/validator.test.ts +611 -85
- package/types/befly.d.ts +9 -15
- package/types/cache.d.ts +73 -0
- package/types/common.d.ts +10 -128
- package/types/database.d.ts +221 -5
- package/types/index.ts +6 -5
- package/types/plugin.d.ts +1 -4
- package/types/redis.d.ts +37 -2
- package/types/table.d.ts +175 -0
- package/config.ts +0 -70
- package/hooks/_rateLimit.ts +0 -64
- package/lib/regexAliases.ts +0 -59
- package/lib/xml.ts +0 -383
- package/tests/validator-advanced.test.ts +0 -653
- package/tests/xml.test.ts +0 -101
- package/types/addon.d.ts +0 -50
- package/types/crypto.d.ts +0 -23
- package/types/jwt.d.ts +0 -99
- package/types/logger.d.ts +0 -43
- package/types/tool.d.ts +0 -67
- package/types/validator.d.ts +0 -43
package/sync/syncApi.ts
CHANGED
|
@@ -13,13 +13,17 @@
|
|
|
13
13
|
import { readdirSync, statSync } from 'node:fs';
|
|
14
14
|
import { join, dirname, relative, basename } from 'pathe';
|
|
15
15
|
import { Connect } from '../lib/connect.js';
|
|
16
|
+
import { DbHelper } from '../lib/dbHelper.js';
|
|
16
17
|
import { RedisHelper } from '../lib/redisHelper.js';
|
|
17
|
-
import {
|
|
18
|
+
import { RedisKeys } from 'befly-shared/redisKeys';
|
|
19
|
+
import { scanFiles } from 'befly-shared/scanFiles';
|
|
20
|
+
import { scanAddons, addonDirExists, getAddonDir } from 'befly-shared/addonHelper';
|
|
18
21
|
|
|
19
22
|
import { Logger } from '../lib/logger.js';
|
|
20
23
|
import { projectDir } from '../paths.js';
|
|
24
|
+
import { beflyConfig } from '../befly.config.js';
|
|
21
25
|
|
|
22
|
-
import type { SyncApiOptions, ApiInfo
|
|
26
|
+
import type { SyncApiOptions, ApiInfo } from '../types/index.js';
|
|
23
27
|
|
|
24
28
|
/**
|
|
25
29
|
* 从 API 文件中提取接口信息
|
|
@@ -59,7 +63,7 @@ async function extractApiInfo(filePath: string, apiRoot: string, type: 'app' | '
|
|
|
59
63
|
addonTitle: addonTitle || addonName
|
|
60
64
|
};
|
|
61
65
|
} catch (error: any) {
|
|
62
|
-
Logger.error('同步 API
|
|
66
|
+
Logger.error({ err: error }, '同步 API 失败');
|
|
63
67
|
throw error;
|
|
64
68
|
}
|
|
65
69
|
}
|
|
@@ -136,7 +140,7 @@ async function scanAllApis(projectRoot: string): Promise<ApiInfo[]> {
|
|
|
136
140
|
|
|
137
141
|
return apis;
|
|
138
142
|
} catch (error: any) {
|
|
139
|
-
Logger.error(
|
|
143
|
+
Logger.error({ err: error }, '接口扫描失败');
|
|
140
144
|
return apis;
|
|
141
145
|
}
|
|
142
146
|
}
|
|
@@ -184,7 +188,7 @@ async function syncApis(helper: any, apis: ApiInfo[]): Promise<void> {
|
|
|
184
188
|
});
|
|
185
189
|
}
|
|
186
190
|
} catch (error: any) {
|
|
187
|
-
Logger.error(
|
|
191
|
+
Logger.error({ err: error, api: api.name }, '同步接口失败');
|
|
188
192
|
}
|
|
189
193
|
}
|
|
190
194
|
}
|
|
@@ -212,7 +216,7 @@ async function deleteObsoleteRecords(helper: any, apiPaths: Set<string>): Promis
|
|
|
212
216
|
/**
|
|
213
217
|
* SyncApi 命令主函数
|
|
214
218
|
*/
|
|
215
|
-
export async function syncApiCommand(
|
|
219
|
+
export async function syncApiCommand(options: SyncApiOptions = {}): Promise<void> {
|
|
216
220
|
try {
|
|
217
221
|
if (options.plan) {
|
|
218
222
|
Logger.debug('[计划] 同步 API 接口到数据库(plan 模式不执行)');
|
|
@@ -220,9 +224,9 @@ export async function syncApiCommand(config: BeflyOptions, options: SyncApiOptio
|
|
|
220
224
|
}
|
|
221
225
|
|
|
222
226
|
// 连接数据库(SQL + Redis)
|
|
223
|
-
await Connect.connect(
|
|
227
|
+
await Connect.connect();
|
|
224
228
|
|
|
225
|
-
const helper = Connect.
|
|
229
|
+
const helper = new DbHelper({ redis: new RedisHelper() } as any, Connect.getSql());
|
|
226
230
|
|
|
227
231
|
// 1. 检查表是否存在(addon_admin_api 来自 addon-admin 组件)
|
|
228
232
|
const exists = await helper.tableExists('addon_admin_api');
|
|
@@ -251,12 +255,12 @@ export async function syncApiCommand(config: BeflyOptions, options: SyncApiOptio
|
|
|
251
255
|
});
|
|
252
256
|
|
|
253
257
|
const redisHelper = new RedisHelper();
|
|
254
|
-
await redisHelper.setObject(
|
|
258
|
+
await redisHelper.setObject(RedisKeys.apisAll(), apiList);
|
|
255
259
|
} catch (error: any) {
|
|
256
260
|
// 忽略缓存错误
|
|
257
261
|
}
|
|
258
262
|
} catch (error: any) {
|
|
259
|
-
Logger.error('API
|
|
263
|
+
Logger.error({ err: error }, 'API 同步失败');
|
|
260
264
|
throw error;
|
|
261
265
|
} finally {
|
|
262
266
|
await Connect.disconnect();
|
package/sync/syncDb/apply.ts
CHANGED
|
@@ -175,8 +175,7 @@ export async function applyTablePlan(sql: SQL, tableName: string, fields: Record
|
|
|
175
175
|
Logger.debug(`[索引变化] 删除索引 ${tableName}.${act.indexName} (${act.fieldName})`);
|
|
176
176
|
}
|
|
177
177
|
} catch (error: any) {
|
|
178
|
-
Logger.error(`${act.action === 'create' ? '创建' : '删除'}
|
|
179
|
-
Logger.warn(`表名: ${tableName}, 索引名: ${act.indexName}, 字段: ${act.fieldName}`);
|
|
178
|
+
Logger.error({ err: error, table: tableName, index: act.indexName, field: act.fieldName }, `${act.action === 'create' ? '创建' : '删除'}索引失败`);
|
|
180
179
|
throw error;
|
|
181
180
|
}
|
|
182
181
|
}
|
package/sync/syncDb.ts
CHANGED
|
@@ -13,7 +13,9 @@ import { snakeCase } from 'es-toolkit/string';
|
|
|
13
13
|
import { Connect } from '../lib/connect.js';
|
|
14
14
|
import { RedisHelper } from '../lib/redisHelper.js';
|
|
15
15
|
import { checkTable } from '../checks/checkTable.js';
|
|
16
|
-
import { scanFiles
|
|
16
|
+
import { scanFiles } from 'befly-shared/scanFiles';
|
|
17
|
+
import { scanAddons, addonDirExists, getAddonDir } from 'befly-shared/addonHelper';
|
|
18
|
+
import { RedisKeys } from 'befly-shared/redisKeys';
|
|
17
19
|
import { Logger } from '../lib/logger.js';
|
|
18
20
|
import { projectDir } from '../paths.js';
|
|
19
21
|
|
|
@@ -24,8 +26,9 @@ import { modifyTable } from './syncDb/table.js';
|
|
|
24
26
|
import { createTable } from './syncDb/tableCreate.js';
|
|
25
27
|
import { applyFieldDefaults } from './syncDb/helpers.js';
|
|
26
28
|
import { setDbType } from './syncDb/constants.js';
|
|
29
|
+
import { beflyConfig } from '../befly.config.js';
|
|
27
30
|
import type { SQL } from 'bun';
|
|
28
|
-
import type {
|
|
31
|
+
import type { SyncDbOptions } from '../types/index.js';
|
|
29
32
|
|
|
30
33
|
// 全局 SQL 客户端实例
|
|
31
34
|
let sql: SQL | null = null;
|
|
@@ -42,20 +45,20 @@ const processedTables: string[] = [];
|
|
|
42
45
|
* 3. 扫描表定义文件(核心表、项目表、addon表)
|
|
43
46
|
* 4. 对比并应用表结构变更
|
|
44
47
|
*/
|
|
45
|
-
export async function syncDbCommand(
|
|
48
|
+
export async function syncDbCommand(options: SyncDbOptions = {}): Promise<void> {
|
|
46
49
|
try {
|
|
47
50
|
// 清空处理记录
|
|
48
51
|
processedTables.length = 0;
|
|
49
52
|
|
|
50
53
|
// 设置数据库类型(从配置获取)
|
|
51
|
-
const dbType =
|
|
54
|
+
const dbType = beflyConfig.db?.type || 'mysql';
|
|
52
55
|
setDbType(dbType);
|
|
53
56
|
|
|
54
57
|
// 验证表定义文件
|
|
55
58
|
await checkTable();
|
|
56
59
|
|
|
57
60
|
// 建立数据库连接并检查版本
|
|
58
|
-
sql = await Connect.connectSql(
|
|
61
|
+
sql = await Connect.connectSql();
|
|
59
62
|
await ensureDbVersion(sql);
|
|
60
63
|
|
|
61
64
|
// 初始化 Redis 连接(用于清理缓存)
|
|
@@ -114,7 +117,7 @@ export async function syncDbCommand(config: BeflyOptions, options: SyncDbOptions
|
|
|
114
117
|
applyFieldDefaults(fieldDef);
|
|
115
118
|
}
|
|
116
119
|
|
|
117
|
-
const dbName =
|
|
120
|
+
const dbName = beflyConfig.db?.database;
|
|
118
121
|
const existsTable = await tableExists(sql!, tableName, dbName);
|
|
119
122
|
|
|
120
123
|
// 读取 force 参数
|
|
@@ -134,17 +137,11 @@ export async function syncDbCommand(config: BeflyOptions, options: SyncDbOptions
|
|
|
134
137
|
// 清理 Redis 缓存(如果有表被处理)
|
|
135
138
|
if (processedTables.length > 0) {
|
|
136
139
|
const redisHelper = new RedisHelper();
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
try {
|
|
140
|
-
await redisHelper.del(cacheKey);
|
|
141
|
-
} catch (error: any) {
|
|
142
|
-
Logger.warn(`清理表 ${tableName} 的缓存失败: ${error.message}`);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
140
|
+
const cacheKeys = processedTables.map((tableName) => RedisKeys.tableColumns(tableName));
|
|
141
|
+
await redisHelper.delBatch(cacheKeys);
|
|
145
142
|
}
|
|
146
143
|
} catch (error: any) {
|
|
147
|
-
Logger.error('数据库同步失败'
|
|
144
|
+
Logger.error({ err: error }, '数据库同步失败');
|
|
148
145
|
throw error;
|
|
149
146
|
} finally {
|
|
150
147
|
if (sql) {
|
package/sync/syncDev.ts
CHANGED
|
@@ -7,33 +7,37 @@
|
|
|
7
7
|
* - 表名: addon_admin_admin
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { scanAddons, getAddonDir, normalizeModuleForSync } from 'befly-
|
|
10
|
+
import { scanAddons, getAddonDir, normalizeModuleForSync } from 'befly-shared/addonHelper';
|
|
11
11
|
|
|
12
12
|
import { Logger } from '../lib/logger.js';
|
|
13
13
|
import { Cipher } from '../lib/cipher.js';
|
|
14
14
|
import { Connect } from '../lib/connect.js';
|
|
15
15
|
import { DbHelper } from '../lib/dbHelper.js';
|
|
16
|
-
import
|
|
16
|
+
import { RedisHelper } from '../lib/redisHelper.js';
|
|
17
|
+
import { CacheHelper } from '../lib/cacheHelper.js';
|
|
18
|
+
import { beflyConfig } from '../befly.config.js';
|
|
19
|
+
|
|
20
|
+
import type { SyncDevOptions, SyncDevStats } from '../types/index.js';
|
|
17
21
|
|
|
18
22
|
/**
|
|
19
23
|
* SyncDev 命令主函数
|
|
20
24
|
*/
|
|
21
|
-
export async function syncDevCommand(
|
|
25
|
+
export async function syncDevCommand(options: SyncDevOptions = {}): Promise<void> {
|
|
22
26
|
try {
|
|
23
27
|
if (options.plan) {
|
|
24
28
|
Logger.debug('[计划] 同步完成后将初始化/更新开发管理员账号(plan 模式不执行)');
|
|
25
29
|
return;
|
|
26
30
|
}
|
|
27
31
|
|
|
28
|
-
if (!
|
|
32
|
+
if (!beflyConfig.devPassword) {
|
|
29
33
|
// 未配置开发者密码,跳过同步
|
|
30
34
|
return;
|
|
31
35
|
}
|
|
32
36
|
|
|
33
37
|
// 连接数据库(SQL + Redis)
|
|
34
|
-
await Connect.connect(
|
|
38
|
+
await Connect.connect();
|
|
35
39
|
|
|
36
|
-
const helper = Connect.
|
|
40
|
+
const helper = new DbHelper({ redis: new RedisHelper() } as any, Connect.getSql());
|
|
37
41
|
|
|
38
42
|
// 检查 addon_admin_admin 表是否存在
|
|
39
43
|
const existAdmin = await helper.tableExists('addon_admin_admin');
|
|
@@ -118,12 +122,12 @@ export async function syncDevCommand(config: BeflyOptions, options: SyncDevOptio
|
|
|
118
122
|
}
|
|
119
123
|
|
|
120
124
|
// 使用 bcrypt 加密密码
|
|
121
|
-
const hashed = await Cipher.hashPassword(
|
|
125
|
+
const hashed = await Cipher.hashPassword(beflyConfig.devPassword);
|
|
122
126
|
|
|
123
127
|
// 准备开发管理员数据
|
|
124
128
|
const devData = {
|
|
125
129
|
nickname: '开发者',
|
|
126
|
-
email:
|
|
130
|
+
email: beflyConfig.devEmail,
|
|
127
131
|
username: 'dev',
|
|
128
132
|
password: hashed,
|
|
129
133
|
roleId: devRole.id,
|
|
@@ -134,14 +138,14 @@ export async function syncDevCommand(config: BeflyOptions, options: SyncDevOptio
|
|
|
134
138
|
// 查询现有账号
|
|
135
139
|
const existing = await helper.getOne({
|
|
136
140
|
table: 'addon_admin_admin',
|
|
137
|
-
where: { email:
|
|
141
|
+
where: { email: beflyConfig.devEmail }
|
|
138
142
|
});
|
|
139
143
|
|
|
140
144
|
if (existing) {
|
|
141
145
|
// 更新现有账号
|
|
142
146
|
await helper.updData({
|
|
143
147
|
table: 'addon_admin_admin',
|
|
144
|
-
where: { email:
|
|
148
|
+
where: { email: beflyConfig.devEmail },
|
|
145
149
|
data: devData
|
|
146
150
|
});
|
|
147
151
|
} else {
|
|
@@ -152,57 +156,16 @@ export async function syncDevCommand(config: BeflyOptions, options: SyncDevOptio
|
|
|
152
156
|
});
|
|
153
157
|
}
|
|
154
158
|
|
|
155
|
-
// 缓存角色权限数据到 Redis
|
|
159
|
+
// 缓存角色权限数据到 Redis(复用 CacheHelper 逻辑)
|
|
156
160
|
try {
|
|
157
|
-
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
if (apiTableExists && roleTableExists) {
|
|
162
|
-
// 查询所有角色
|
|
163
|
-
const roles = await helper.getAll({
|
|
164
|
-
table: 'addon_admin_role',
|
|
165
|
-
fields: ['id', 'code', 'apis']
|
|
166
|
-
});
|
|
167
|
-
|
|
168
|
-
// 查询所有接口
|
|
169
|
-
const allApis = await helper.getAll({
|
|
170
|
-
table: 'addon_admin_api',
|
|
171
|
-
fields: ['id', 'name', 'path', 'method', 'description', 'addonName']
|
|
172
|
-
});
|
|
173
|
-
|
|
174
|
-
const redis = Connect.getRedis();
|
|
175
|
-
|
|
176
|
-
// 为每个角色缓存接口权限
|
|
177
|
-
for (const role of roles) {
|
|
178
|
-
if (!role.apis) continue;
|
|
179
|
-
|
|
180
|
-
// 解析角色的接口 ID 列表
|
|
181
|
-
const apiIds = role.apis
|
|
182
|
-
.split(',')
|
|
183
|
-
.map((id: string) => parseInt(id.trim()))
|
|
184
|
-
.filter((id: number) => !isNaN(id));
|
|
185
|
-
|
|
186
|
-
// 根据 ID 过滤出接口路径
|
|
187
|
-
const roleApiPaths = allApis.filter((api: any) => apiIds.includes(api.id)).map((api: any) => `${api.method}${api.path}`);
|
|
188
|
-
|
|
189
|
-
if (roleApiPaths.length === 0) continue;
|
|
190
|
-
|
|
191
|
-
// 使用 Redis Set 缓存角色权限
|
|
192
|
-
const redisKey = `role:apis:${role.code}`;
|
|
193
|
-
|
|
194
|
-
// 先删除旧数据
|
|
195
|
-
await redis.del(redisKey);
|
|
196
|
-
|
|
197
|
-
// 批量添加到 Set(使用扩展运算符展开数组)
|
|
198
|
-
await redis.sadd(redisKey, ...roleApiPaths);
|
|
199
|
-
}
|
|
200
|
-
}
|
|
161
|
+
const tempBefly = { db: helper, redis: new RedisHelper() } as any;
|
|
162
|
+
const cacheHelper = new CacheHelper(tempBefly);
|
|
163
|
+
await cacheHelper.cacheRolePermissions();
|
|
201
164
|
} catch (error: any) {
|
|
202
165
|
// 忽略缓存错误
|
|
203
166
|
}
|
|
204
167
|
} catch (error: any) {
|
|
205
|
-
Logger.error('同步开发者管理员失败'
|
|
168
|
+
Logger.error({ err: error }, '同步开发者管理员失败');
|
|
206
169
|
throw error;
|
|
207
170
|
} finally {
|
|
208
171
|
await Connect.disconnect();
|