befly 3.8.19 → 3.8.20
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 +7 -6
- package/bunfig.toml +1 -1
- package/lib/database.ts +28 -25
- package/lib/dbHelper.ts +3 -3
- package/lib/jwt.ts +90 -99
- package/lib/logger.ts +44 -23
- package/lib/redisHelper.ts +19 -22
- package/loader/loadApis.ts +69 -114
- package/loader/loadHooks.ts +65 -0
- package/loader/loadPlugins.ts +50 -219
- package/main.ts +106 -133
- package/package.json +15 -7
- package/paths.ts +20 -0
- package/plugins/cache.ts +1 -3
- package/plugins/db.ts +8 -11
- package/plugins/logger.ts +5 -3
- package/plugins/redis.ts +10 -14
- package/router/api.ts +60 -106
- package/router/root.ts +15 -12
- package/router/static.ts +54 -58
- package/sync/syncAll.ts +58 -0
- package/sync/syncApi.ts +264 -0
- package/sync/syncDb/apply.ts +194 -0
- package/sync/syncDb/constants.ts +76 -0
- package/sync/syncDb/ddl.ts +194 -0
- package/sync/syncDb/helpers.ts +200 -0
- package/sync/syncDb/index.ts +164 -0
- package/sync/syncDb/schema.ts +201 -0
- package/sync/syncDb/sqlite.ts +50 -0
- package/sync/syncDb/table.ts +321 -0
- package/sync/syncDb/tableCreate.ts +146 -0
- package/sync/syncDb/version.ts +72 -0
- package/sync/syncDb.ts +19 -0
- package/sync/syncDev.ts +206 -0
- package/sync/syncMenu.ts +331 -0
- package/tsconfig.json +2 -4
- package/types/api.d.ts +6 -0
- package/types/befly.d.ts +152 -28
- package/types/context.d.ts +29 -3
- package/types/hook.d.ts +35 -0
- package/types/index.ts +14 -1
- package/types/plugin.d.ts +6 -7
- package/types/sync.d.ts +403 -0
- package/check.ts +0 -378
- package/env.ts +0 -106
- package/lib/middleware.ts +0 -275
- package/types/env.ts +0 -65
- package/types/util.d.ts +0 -45
- package/util.ts +0 -257
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* syncDb 表创建模块
|
|
3
|
+
*
|
|
4
|
+
* 包含:
|
|
5
|
+
* - 创建表(包含系统字段和业务字段)
|
|
6
|
+
* - 添加 PostgreSQL 注释
|
|
7
|
+
* - 创建表索引
|
|
8
|
+
*
|
|
9
|
+
* 注意:此模块从 table.ts 中提取,用于解除循环依赖
|
|
10
|
+
*/
|
|
11
|
+
import { snakeCase } from 'es-toolkit/string';
|
|
12
|
+
import { Logger } from '../../lib/logger.js';
|
|
13
|
+
import { IS_MYSQL, IS_PG, MYSQL_TABLE_CONFIG } from './constants.js';
|
|
14
|
+
import { quoteIdentifier } from './helpers.js';
|
|
15
|
+
import { buildSystemColumnDefs, buildBusinessColumnDefs, buildIndexSQL } from './ddl.js';
|
|
16
|
+
|
|
17
|
+
import type { SQL } from 'bun';
|
|
18
|
+
import type { FieldDefinition } from 'befly/types/common';
|
|
19
|
+
|
|
20
|
+
// 是否为计划模式(从环境变量读取)
|
|
21
|
+
const IS_PLAN = process.argv.includes('--plan');
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* 为 PostgreSQL 表添加列注释
|
|
25
|
+
*
|
|
26
|
+
* @param sql - SQL 客户端实例
|
|
27
|
+
* @param tableName - 表名
|
|
28
|
+
* @param fields - 字段定义对象
|
|
29
|
+
*/
|
|
30
|
+
async function addPostgresComments(sql: SQL, tableName: string, fields: Record<string, FieldDefinition>): Promise<void> {
|
|
31
|
+
// 系统字段注释
|
|
32
|
+
const systemComments = [
|
|
33
|
+
['id', '主键ID'],
|
|
34
|
+
['created_at', '创建时间'],
|
|
35
|
+
['updated_at', '更新时间'],
|
|
36
|
+
['deleted_at', '删除时间'],
|
|
37
|
+
['state', '状态字段']
|
|
38
|
+
];
|
|
39
|
+
|
|
40
|
+
for (const [name, comment] of systemComments) {
|
|
41
|
+
const stmt = `COMMENT ON COLUMN "${tableName}"."${name}" IS '${comment}'`;
|
|
42
|
+
if (IS_PLAN) {
|
|
43
|
+
Logger.debug(`[计划] ${stmt}`);
|
|
44
|
+
} else {
|
|
45
|
+
await sql.unsafe(stmt);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// 业务字段注释
|
|
50
|
+
for (const [fieldKey, fieldDef] of Object.entries(fields)) {
|
|
51
|
+
// 转换字段名为下划线格式
|
|
52
|
+
const dbFieldName = snakeCase(fieldKey);
|
|
53
|
+
|
|
54
|
+
const { name: fieldName } = fieldDef;
|
|
55
|
+
const stmt = `COMMENT ON COLUMN "${tableName}"."${dbFieldName}" IS '${fieldName}'`;
|
|
56
|
+
if (IS_PLAN) {
|
|
57
|
+
Logger.debug(`[计划] ${stmt}`);
|
|
58
|
+
} else {
|
|
59
|
+
await sql.unsafe(stmt);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* 创建表的索引(并行执行以提升性能)
|
|
66
|
+
*
|
|
67
|
+
* @param sql - SQL 客户端实例
|
|
68
|
+
* @param tableName - 表名
|
|
69
|
+
* @param fields - 字段定义对象
|
|
70
|
+
* @param systemIndexFields - 系统字段索引列表
|
|
71
|
+
*/
|
|
72
|
+
async function createTableIndexes(sql: SQL, tableName: string, fields: Record<string, FieldDefinition>, systemIndexFields: string[]): Promise<void> {
|
|
73
|
+
const indexTasks: Promise<void>[] = [];
|
|
74
|
+
|
|
75
|
+
// 系统字段索引
|
|
76
|
+
for (const sysField of systemIndexFields) {
|
|
77
|
+
const stmt = buildIndexSQL(tableName, `idx_${sysField}`, sysField, 'create');
|
|
78
|
+
if (IS_PLAN) {
|
|
79
|
+
Logger.debug(`[计划] ${stmt}`);
|
|
80
|
+
} else {
|
|
81
|
+
indexTasks.push(sql.unsafe(stmt));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
// 业务字段索引
|
|
86
|
+
for (const [fieldKey, fieldDef] of Object.entries(fields)) {
|
|
87
|
+
// 转换字段名为下划线格式
|
|
88
|
+
const dbFieldName = snakeCase(fieldKey);
|
|
89
|
+
|
|
90
|
+
if (fieldDef.index === true) {
|
|
91
|
+
const stmt = buildIndexSQL(tableName, `idx_${dbFieldName}`, dbFieldName, 'create');
|
|
92
|
+
if (IS_PLAN) {
|
|
93
|
+
Logger.debug(`[计划] ${stmt}`);
|
|
94
|
+
} else {
|
|
95
|
+
indexTasks.push(sql.unsafe(stmt));
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// 并行执行所有索引创建
|
|
101
|
+
if (indexTasks.length > 0) {
|
|
102
|
+
await Promise.all(indexTasks);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* 创建表(包含系统字段和业务字段)
|
|
108
|
+
*
|
|
109
|
+
* @param sql - SQL 客户端实例
|
|
110
|
+
* @param tableName - 表名
|
|
111
|
+
* @param fields - 字段定义对象
|
|
112
|
+
* @param systemIndexFields - 系统字段索引列表(可选,默认使用 ['created_at', 'updated_at', 'state'])
|
|
113
|
+
*/
|
|
114
|
+
export async function createTable(sql: SQL, tableName: string, fields: Record<string, FieldDefinition>, systemIndexFields: string[] = ['created_at', 'updated_at', 'state']): Promise<void> {
|
|
115
|
+
// 构建列定义
|
|
116
|
+
const colDefs = [...buildSystemColumnDefs(), ...buildBusinessColumnDefs(fields)];
|
|
117
|
+
|
|
118
|
+
// 生成 CREATE TABLE 语句
|
|
119
|
+
const cols = colDefs.join(',\n ');
|
|
120
|
+
const tableQuoted = quoteIdentifier(tableName);
|
|
121
|
+
const { ENGINE, CHARSET, COLLATE } = MYSQL_TABLE_CONFIG;
|
|
122
|
+
const createSQL = IS_MYSQL
|
|
123
|
+
? `CREATE TABLE ${tableQuoted} (
|
|
124
|
+
${cols}
|
|
125
|
+
) ENGINE=${ENGINE} DEFAULT CHARSET=${CHARSET} COLLATE=${COLLATE}`
|
|
126
|
+
: `CREATE TABLE ${tableQuoted} (
|
|
127
|
+
${cols}
|
|
128
|
+
)`;
|
|
129
|
+
|
|
130
|
+
if (IS_PLAN) {
|
|
131
|
+
Logger.debug(`[计划] ${createSQL.replace(/\n+/g, ' ')}`);
|
|
132
|
+
} else {
|
|
133
|
+
await sql.unsafe(createSQL);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// PostgreSQL: 添加列注释
|
|
137
|
+
if (IS_PG && !IS_PLAN) {
|
|
138
|
+
await addPostgresComments(sql, tableName, fields);
|
|
139
|
+
} else if (IS_PG && IS_PLAN) {
|
|
140
|
+
// 计划模式也要输出注释语句
|
|
141
|
+
await addPostgresComments(sql, tableName, fields);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// 创建索引
|
|
145
|
+
await createTableIndexes(sql, tableName, fields, systemIndexFields);
|
|
146
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* syncDb 数据库版本检查模块
|
|
3
|
+
*
|
|
4
|
+
* 包含:
|
|
5
|
+
* - 数据库版本验证(MySQL/PostgreSQL/SQLite)
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { Logger } from '../../lib/logger.js';
|
|
9
|
+
import { DB_VERSION_REQUIREMENTS, IS_MYSQL, IS_PG, IS_SQLITE } from './constants.js';
|
|
10
|
+
import type { SQL } from 'bun';
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* 数据库版本检查(按方言)
|
|
14
|
+
*
|
|
15
|
+
* 根据当前数据库类型检查版本是否符合最低要求:
|
|
16
|
+
* - MySQL: >= 8.0
|
|
17
|
+
* - PostgreSQL: >= 17
|
|
18
|
+
* - SQLite: >= 3.50.0
|
|
19
|
+
*
|
|
20
|
+
* @param sql - SQL 客户端实例
|
|
21
|
+
* @throws {Error} 如果数据库版本不符合要求或无法获取版本信息
|
|
22
|
+
*/
|
|
23
|
+
export async function ensureDbVersion(sql: SQL): Promise<void> {
|
|
24
|
+
if (!sql) throw new Error('SQL 客户端未初始化');
|
|
25
|
+
|
|
26
|
+
if (IS_MYSQL) {
|
|
27
|
+
const r = await sql`SELECT VERSION() AS version`;
|
|
28
|
+
if (!r || r.length === 0 || !r[0]?.version) {
|
|
29
|
+
throw new Error('无法获取 MySQL 版本信息');
|
|
30
|
+
}
|
|
31
|
+
const version = r[0].version;
|
|
32
|
+
const majorVersion = parseInt(String(version).split('.')[0], 10);
|
|
33
|
+
if (!Number.isFinite(majorVersion) || majorVersion < DB_VERSION_REQUIREMENTS.MYSQL_MIN_MAJOR) {
|
|
34
|
+
throw new Error(`此脚本仅支持 MySQL ${DB_VERSION_REQUIREMENTS.MYSQL_MIN_MAJOR}.0+,当前版本: ${version}`);
|
|
35
|
+
}
|
|
36
|
+
Logger.debug(`MySQL 版本: ${version}`);
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
if (IS_PG) {
|
|
41
|
+
const r = await sql`SELECT version() AS version`;
|
|
42
|
+
if (!r || r.length === 0 || !r[0]?.version) {
|
|
43
|
+
throw new Error('无法获取 PostgreSQL 版本信息');
|
|
44
|
+
}
|
|
45
|
+
const versionText = r[0].version;
|
|
46
|
+
Logger.debug(`PostgreSQL 版本: ${versionText}`);
|
|
47
|
+
const m = /PostgreSQL\s+(\d+)/i.exec(versionText);
|
|
48
|
+
const major = m ? parseInt(m[1], 10) : NaN;
|
|
49
|
+
if (!Number.isFinite(major) || major < DB_VERSION_REQUIREMENTS.POSTGRES_MIN_MAJOR) {
|
|
50
|
+
throw new Error(`此脚本要求 PostgreSQL >= ${DB_VERSION_REQUIREMENTS.POSTGRES_MIN_MAJOR},当前: ${versionText}`);
|
|
51
|
+
}
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (IS_SQLITE) {
|
|
56
|
+
const r = await sql`SELECT sqlite_version() AS version`;
|
|
57
|
+
if (!r || r.length === 0 || !r[0]?.version) {
|
|
58
|
+
throw new Error('无法获取 SQLite 版本信息');
|
|
59
|
+
}
|
|
60
|
+
const version = r[0].version;
|
|
61
|
+
Logger.debug(`SQLite 版本: ${version}`);
|
|
62
|
+
// 强制最低版本:SQLite ≥ 3.50.0
|
|
63
|
+
const [maj, min, patch] = String(version)
|
|
64
|
+
.split('.')
|
|
65
|
+
.map((v) => parseInt(v, 10) || 0);
|
|
66
|
+
const vnum = maj * 10000 + min * 100 + patch; // 3.50.0 -> 35000
|
|
67
|
+
if (!Number.isFinite(vnum) || vnum < DB_VERSION_REQUIREMENTS.SQLITE_MIN_VERSION_NUM) {
|
|
68
|
+
throw new Error(`此脚本要求 SQLite >= ${DB_VERSION_REQUIREMENTS.SQLITE_MIN_VERSION},当前: ${version}`);
|
|
69
|
+
}
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
}
|
package/sync/syncDb.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SyncDb 命令 - 同步数据库表结构
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { join } from 'pathe';
|
|
6
|
+
import { existsSync } from 'node:fs';
|
|
7
|
+
import { Logger } from '../lib/logger.js';
|
|
8
|
+
import { SyncDb } from './syncDb/index.js';
|
|
9
|
+
import type { SyncDbOptions, BeflyOptions } from '../types/index.js';
|
|
10
|
+
|
|
11
|
+
export async function syncDbCommand(config: BeflyOptions, options: SyncDbOptions): Promise<void> {
|
|
12
|
+
try {
|
|
13
|
+
// 执行同步
|
|
14
|
+
await SyncDb(config, options);
|
|
15
|
+
} catch (error: any) {
|
|
16
|
+
Logger.error('数据库同步失败', error);
|
|
17
|
+
throw error;
|
|
18
|
+
}
|
|
19
|
+
}
|
package/sync/syncDev.ts
ADDED
|
@@ -0,0 +1,206 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SyncDev 命令 - 同步开发者管理员到数据库
|
|
3
|
+
* - 邮箱: 通过 DEV_EMAIL 环境变量配置(默认 dev@qq.com)
|
|
4
|
+
* - 姓名: 开发者
|
|
5
|
+
* - 密码: 使用 bcrypt 加密,通过 DEV_PASSWORD 环境变量配置
|
|
6
|
+
* - 角色: roleCode=dev, roleType=admin
|
|
7
|
+
* - 表名: addon_admin_admin
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { Database } from '../lib/database.js';
|
|
11
|
+
import { Cipher } from '../lib/cipher.js';
|
|
12
|
+
import { Logger } from '../lib/logger.js';
|
|
13
|
+
import type { SyncDevOptions, SyncDevStats, BeflyOptions } from '../types/index.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* SyncDev 命令主函数
|
|
17
|
+
*/
|
|
18
|
+
export async function syncDevCommand(config: BeflyOptions, options: SyncDevOptions = {}): Promise<void> {
|
|
19
|
+
try {
|
|
20
|
+
if (options.plan) {
|
|
21
|
+
Logger.debug('[计划] 同步完成后将初始化/更新开发管理员账号(plan 模式不执行)');
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const devPassword = config.devPassword;
|
|
26
|
+
const devEmail = config.devEmail || 'dev@qq.com';
|
|
27
|
+
|
|
28
|
+
if (!devPassword) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// 连接数据库(SQL + Redis)
|
|
33
|
+
await Database.connect();
|
|
34
|
+
|
|
35
|
+
const helper = Database.getDbHelper();
|
|
36
|
+
|
|
37
|
+
// 检查 addon_admin_admin 表是否存在
|
|
38
|
+
const existAdmin = await helper.tableExists('addon_admin_admin');
|
|
39
|
+
if (!existAdmin) {
|
|
40
|
+
return;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// 检查 addon_admin_role 表是否存在
|
|
44
|
+
const existRole = await helper.tableExists('addon_admin_role');
|
|
45
|
+
if (!existRole) {
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// 检查 addon_admin_menu 表是否存在
|
|
50
|
+
const existMenu = await helper.tableExists('addon_admin_menu');
|
|
51
|
+
if (!existMenu) {
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 查询所有菜单 ID
|
|
56
|
+
const allMenus = await helper.getAll({
|
|
57
|
+
table: 'addon_admin_menu',
|
|
58
|
+
fields: ['id']
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
if (!allMenus || !Array.isArray(allMenus)) {
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const menuIds = allMenus.length > 0 ? allMenus.map((m: any) => m.id).join(',') : '';
|
|
66
|
+
|
|
67
|
+
// 查询所有接口 ID
|
|
68
|
+
const existApi = await helper.tableExists('addon_admin_api');
|
|
69
|
+
let apiIds = '';
|
|
70
|
+
if (existApi) {
|
|
71
|
+
const allApis = await helper.getAll({
|
|
72
|
+
table: 'addon_admin_api',
|
|
73
|
+
fields: ['id']
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
if (allApis && Array.isArray(allApis) && allApis.length > 0) {
|
|
77
|
+
apiIds = allApis.map((a: any) => a.id).join(',');
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// 查询或创建 dev 角色
|
|
82
|
+
let devRole = await helper.getOne({
|
|
83
|
+
table: 'addon_admin_role',
|
|
84
|
+
where: { code: 'dev' }
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
if (devRole) {
|
|
88
|
+
// 更新 dev 角色的菜单和接口权限
|
|
89
|
+
await helper.updData({
|
|
90
|
+
table: 'addon_admin_role',
|
|
91
|
+
where: { code: 'dev' },
|
|
92
|
+
data: {
|
|
93
|
+
name: '开发者角色',
|
|
94
|
+
description: '拥有所有菜单和接口权限的开发者角色',
|
|
95
|
+
menus: menuIds,
|
|
96
|
+
apis: apiIds
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
} else {
|
|
100
|
+
// 创建 dev 角色
|
|
101
|
+
const roleId = await helper.insData({
|
|
102
|
+
table: 'addon_admin_role',
|
|
103
|
+
data: {
|
|
104
|
+
name: '开发者角色',
|
|
105
|
+
code: 'dev',
|
|
106
|
+
description: '拥有所有菜单和接口权限的开发者角色',
|
|
107
|
+
menus: menuIds,
|
|
108
|
+
apis: apiIds,
|
|
109
|
+
sort: 0
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
devRole = { id: roleId };
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
// 使用 bcrypt 加密密码
|
|
116
|
+
const hashed = await Cipher.hashPassword(devPassword);
|
|
117
|
+
|
|
118
|
+
// 准备开发管理员数据
|
|
119
|
+
const devData = {
|
|
120
|
+
name: '开发者',
|
|
121
|
+
nickname: '开发者',
|
|
122
|
+
email: devEmail,
|
|
123
|
+
username: 'dev',
|
|
124
|
+
password: hashed,
|
|
125
|
+
roleId: devRole.id,
|
|
126
|
+
roleCode: 'dev',
|
|
127
|
+
roleType: 'admin'
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
// 查询现有账号
|
|
131
|
+
const existing = await helper.getOne({
|
|
132
|
+
table: 'addon_admin_admin',
|
|
133
|
+
where: { email: devEmail }
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
if (existing) {
|
|
137
|
+
// 更新现有账号
|
|
138
|
+
await helper.updData({
|
|
139
|
+
table: 'addon_admin_admin',
|
|
140
|
+
where: { email: devEmail },
|
|
141
|
+
data: devData
|
|
142
|
+
});
|
|
143
|
+
} else {
|
|
144
|
+
// 插入新账号
|
|
145
|
+
await helper.insData({
|
|
146
|
+
table: 'addon_admin_admin',
|
|
147
|
+
data: devData
|
|
148
|
+
});
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// 缓存角色权限数据到 Redis
|
|
152
|
+
try {
|
|
153
|
+
// 检查必要的表是否存在
|
|
154
|
+
const apiTableExists = await helper.tableExists('addon_admin_api');
|
|
155
|
+
const roleTableExists = await helper.tableExists('addon_admin_role');
|
|
156
|
+
|
|
157
|
+
if (apiTableExists && roleTableExists) {
|
|
158
|
+
// 查询所有角色
|
|
159
|
+
const roles = await helper.getAll({
|
|
160
|
+
table: 'addon_admin_role',
|
|
161
|
+
fields: ['id', 'code', 'apis']
|
|
162
|
+
});
|
|
163
|
+
|
|
164
|
+
// 查询所有接口
|
|
165
|
+
const allApis = await helper.getAll({
|
|
166
|
+
table: 'addon_admin_api',
|
|
167
|
+
fields: ['id', 'name', 'path', 'method', 'description', 'addonName']
|
|
168
|
+
});
|
|
169
|
+
|
|
170
|
+
const redis = Database.getRedis();
|
|
171
|
+
|
|
172
|
+
// 为每个角色缓存接口权限
|
|
173
|
+
for (const role of roles) {
|
|
174
|
+
if (!role.apis) continue;
|
|
175
|
+
|
|
176
|
+
// 解析角色的接口 ID 列表
|
|
177
|
+
const apiIds = role.apis
|
|
178
|
+
.split(',')
|
|
179
|
+
.map((id: string) => parseInt(id.trim()))
|
|
180
|
+
.filter((id: number) => !isNaN(id));
|
|
181
|
+
|
|
182
|
+
// 根据 ID 过滤出接口路径
|
|
183
|
+
const roleApiPaths = allApis.filter((api: any) => apiIds.includes(api.id)).map((api: any) => `${api.method}${api.path}`);
|
|
184
|
+
|
|
185
|
+
if (roleApiPaths.length === 0) continue;
|
|
186
|
+
|
|
187
|
+
// 使用 Redis Set 缓存角色权限
|
|
188
|
+
const redisKey = `role:apis:${role.code}`;
|
|
189
|
+
|
|
190
|
+
// 先删除旧数据
|
|
191
|
+
await redis.del(redisKey);
|
|
192
|
+
|
|
193
|
+
// 批量添加到 Set(使用扩展运算符展开数组)
|
|
194
|
+
await redis.sadd(redisKey, ...roleApiPaths);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
} catch (error: any) {
|
|
198
|
+
// 忽略缓存错误
|
|
199
|
+
}
|
|
200
|
+
} catch (error: any) {
|
|
201
|
+
Logger.error('同步开发者管理员失败', error);
|
|
202
|
+
throw error;
|
|
203
|
+
} finally {
|
|
204
|
+
await Database?.disconnect();
|
|
205
|
+
}
|
|
206
|
+
}
|