befly 3.5.7 → 3.6.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/lib/addon.ts +77 -0
- package/lib/logger.ts +6 -15
- package/lifecycle/checker.ts +20 -49
- package/lifecycle/lifecycle.ts +7 -5
- package/lifecycle/loader.ts +5 -5
- package/main.ts +10 -1
- package/package.json +2 -9
- package/paths.ts +5 -54
- package/util.ts +1 -83
- package/apis/admin/del.ts +0 -35
- package/apis/admin/info.ts +0 -50
- package/apis/admin/ins.ts +0 -61
- package/apis/admin/list.ts +0 -20
- package/apis/admin/roleDetail.ts +0 -35
- package/apis/admin/roleSave.ts +0 -40
- package/apis/admin/upd.ts +0 -51
- package/apis/api/all.ts +0 -37
- package/apis/auth/login.ts +0 -78
- package/apis/auth/logout.ts +0 -23
- package/apis/auth/register.ts +0 -50
- package/apis/auth/sendSmsCode.ts +0 -36
- package/apis/cache/refresh.ts +0 -34
- package/apis/dashboard/addonList.ts +0 -47
- package/apis/dashboard/changelog.ts +0 -37
- package/apis/dashboard/configStatus.ts +0 -54
- package/apis/dashboard/environmentInfo.ts +0 -46
- package/apis/dashboard/performanceMetrics.ts +0 -23
- package/apis/dashboard/permissionStats.ts +0 -31
- package/apis/dashboard/serviceStatus.ts +0 -82
- package/apis/dashboard/systemInfo.ts +0 -26
- package/apis/dashboard/systemOverview.ts +0 -32
- package/apis/dashboard/systemResources.ts +0 -119
- package/apis/dict/all.ts +0 -25
- package/apis/dict/del.ts +0 -19
- package/apis/dict/detail.ts +0 -21
- package/apis/dict/ins.ts +0 -27
- package/apis/dict/list.ts +0 -18
- package/apis/dict/upd.ts +0 -31
- package/apis/menu/all.ts +0 -68
- package/apis/menu/del.ts +0 -37
- package/apis/menu/ins.ts +0 -20
- package/apis/menu/list.ts +0 -21
- package/apis/menu/upd.ts +0 -29
- package/apis/role/apiDetail.ts +0 -30
- package/apis/role/apiSave.ts +0 -41
- package/apis/role/del.ts +0 -44
- package/apis/role/detail.ts +0 -24
- package/apis/role/ins.ts +0 -39
- package/apis/role/list.ts +0 -14
- package/apis/role/menuDetail.ts +0 -30
- package/apis/role/menuSave.ts +0 -38
- package/apis/role/save.ts +0 -44
- package/apis/role/upd.ts +0 -40
- package/bin/index.ts +0 -34
- package/checks/conflict.ts +0 -351
- package/checks/table.ts +0 -250
- package/commands/index.ts +0 -73
- package/commands/sync.ts +0 -88
- package/commands/syncApi.ts +0 -316
- package/commands/syncDb/apply.ts +0 -171
- package/commands/syncDb/constants.ts +0 -77
- package/commands/syncDb/ddl.ts +0 -191
- package/commands/syncDb/helpers.ts +0 -173
- package/commands/syncDb/index.ts +0 -217
- package/commands/syncDb/schema.ts +0 -199
- package/commands/syncDb/sqlite.ts +0 -50
- package/commands/syncDb/state.ts +0 -112
- package/commands/syncDb/table.ts +0 -214
- package/commands/syncDb/tableCreate.ts +0 -149
- package/commands/syncDb/types.ts +0 -92
- package/commands/syncDb/version.ts +0 -73
- package/commands/syncDb.ts +0 -34
- package/commands/syncDev.ts +0 -237
- package/commands/syncMenu.ts +0 -349
- package/commands/util.ts +0 -58
- package/tables/admin.json +0 -14
- package/tables/api.json +0 -8
- package/tables/dict.json +0 -8
- package/tables/menu.json +0 -8
- package/tables/role.json +0 -8
|
@@ -1,149 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* syncDb 表创建模块
|
|
3
|
-
*
|
|
4
|
-
* 包含:
|
|
5
|
-
* - 创建表(包含系统字段和业务字段)
|
|
6
|
-
* - 添加 PostgreSQL 注释
|
|
7
|
-
* - 创建表索引
|
|
8
|
-
*
|
|
9
|
-
* 注意:此模块从 table.ts 中提取,用于解除循环依赖
|
|
10
|
-
*/
|
|
11
|
-
import { snakeCase } from 'es-toolkit/string';
|
|
12
|
-
import { parseRule } from '../../util.js';
|
|
13
|
-
import { Logger } from '../../lib/logger.js';
|
|
14
|
-
import { IS_MYSQL, IS_PG, MYSQL_TABLE_CONFIG } from './constants.js';
|
|
15
|
-
import { quoteIdentifier } from './helpers.js';
|
|
16
|
-
import { buildSystemColumnDefs, buildBusinessColumnDefs, buildIndexSQL } from './ddl.js';
|
|
17
|
-
|
|
18
|
-
import type { SQL } from 'bun';
|
|
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, string>): 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.info(`[计划] ${stmt}`);
|
|
44
|
-
} else {
|
|
45
|
-
await sql.unsafe(stmt);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
// 业务字段注释
|
|
50
|
-
for (const [fieldKey, fieldRule] of Object.entries(fields)) {
|
|
51
|
-
// 转换字段名为下划线格式
|
|
52
|
-
const dbFieldName = snakeCase(fieldKey);
|
|
53
|
-
|
|
54
|
-
const parsed = parseRule(fieldRule);
|
|
55
|
-
const { name: fieldName } = parsed;
|
|
56
|
-
const stmt = `COMMENT ON COLUMN "${tableName}"."${dbFieldName}" IS '${fieldName}'`;
|
|
57
|
-
if (IS_PLAN) {
|
|
58
|
-
Logger.info(`[计划] ${stmt}`);
|
|
59
|
-
} else {
|
|
60
|
-
await sql.unsafe(stmt);
|
|
61
|
-
}
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* 创建表的索引(并行执行以提升性能)
|
|
67
|
-
*
|
|
68
|
-
* @param sql - SQL 客户端实例
|
|
69
|
-
* @param tableName - 表名
|
|
70
|
-
* @param fields - 字段定义对象
|
|
71
|
-
* @param systemIndexFields - 系统字段索引列表
|
|
72
|
-
*/
|
|
73
|
-
async function createTableIndexes(sql: SQL, tableName: string, fields: Record<string, string>, systemIndexFields: string[]): Promise<void> {
|
|
74
|
-
const indexTasks: Promise<void>[] = [];
|
|
75
|
-
|
|
76
|
-
// 系统字段索引
|
|
77
|
-
for (const sysField of systemIndexFields) {
|
|
78
|
-
const stmt = buildIndexSQL(tableName, `idx_${sysField}`, sysField, 'create');
|
|
79
|
-
if (IS_PLAN) {
|
|
80
|
-
Logger.info(`[计划] ${stmt}`);
|
|
81
|
-
} else {
|
|
82
|
-
indexTasks.push(sql.unsafe(stmt));
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
// 业务字段索引
|
|
87
|
-
for (const [fieldKey, fieldRule] of Object.entries(fields)) {
|
|
88
|
-
// 转换字段名为下划线格式
|
|
89
|
-
const dbFieldName = snakeCase(fieldKey);
|
|
90
|
-
|
|
91
|
-
const parsed = parseRule(fieldRule);
|
|
92
|
-
if (parsed.index === 1) {
|
|
93
|
-
const stmt = buildIndexSQL(tableName, `idx_${dbFieldName}`, dbFieldName, 'create');
|
|
94
|
-
if (IS_PLAN) {
|
|
95
|
-
Logger.info(`[计划] ${stmt}`);
|
|
96
|
-
} else {
|
|
97
|
-
indexTasks.push(sql.unsafe(stmt));
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
// 并行执行所有索引创建
|
|
103
|
-
if (indexTasks.length > 0) {
|
|
104
|
-
await Promise.all(indexTasks);
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
/**
|
|
109
|
-
* 创建表(包含系统字段和业务字段)
|
|
110
|
-
*
|
|
111
|
-
* @param sql - SQL 客户端实例
|
|
112
|
-
* @param tableName - 表名
|
|
113
|
-
* @param fields - 字段定义对象
|
|
114
|
-
* @param systemIndexFields - 系统字段索引列表(可选,默认使用 ['created_at', 'updated_at', 'state'])
|
|
115
|
-
*/
|
|
116
|
-
export async function createTable(sql: SQL, tableName: string, fields: Record<string, string>, systemIndexFields: string[] = ['created_at', 'updated_at', 'state']): Promise<void> {
|
|
117
|
-
// 构建列定义
|
|
118
|
-
const colDefs = [...buildSystemColumnDefs(), ...buildBusinessColumnDefs(fields)];
|
|
119
|
-
|
|
120
|
-
// 生成 CREATE TABLE 语句
|
|
121
|
-
const cols = colDefs.join(',\n ');
|
|
122
|
-
const tableQuoted = quoteIdentifier(tableName);
|
|
123
|
-
const { ENGINE, CHARSET, COLLATE } = MYSQL_TABLE_CONFIG;
|
|
124
|
-
const createSQL = IS_MYSQL
|
|
125
|
-
? `CREATE TABLE ${tableQuoted} (
|
|
126
|
-
${cols}
|
|
127
|
-
) ENGINE=${ENGINE} DEFAULT CHARSET=${CHARSET} COLLATE=${COLLATE}`
|
|
128
|
-
: `CREATE TABLE ${tableQuoted} (
|
|
129
|
-
${cols}
|
|
130
|
-
)`;
|
|
131
|
-
|
|
132
|
-
if (IS_PLAN) {
|
|
133
|
-
Logger.info(`[计划] ${createSQL.replace(/\n+/g, ' ')}`);
|
|
134
|
-
} else {
|
|
135
|
-
await sql.unsafe(createSQL);
|
|
136
|
-
Logger.info(` ✓ 新建表`);
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// PostgreSQL: 添加列注释
|
|
140
|
-
if (IS_PG && !IS_PLAN) {
|
|
141
|
-
await addPostgresComments(sql, tableName, fields);
|
|
142
|
-
} else if (IS_PG && IS_PLAN) {
|
|
143
|
-
// 计划模式也要输出注释语句
|
|
144
|
-
await addPostgresComments(sql, tableName, fields);
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// 创建索引
|
|
148
|
-
await createTableIndexes(sql, tableName, fields, systemIndexFields);
|
|
149
|
-
}
|
package/commands/syncDb/types.ts
DELETED
|
@@ -1,92 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* syncDb 类型定义模块
|
|
3
|
-
*
|
|
4
|
-
* 集中管理核心类型定义
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
// ==================== 数据库相关类型 ====================
|
|
8
|
-
|
|
9
|
-
/**
|
|
10
|
-
* 列信息接口
|
|
11
|
-
*/
|
|
12
|
-
export interface ColumnInfo {
|
|
13
|
-
type: string;
|
|
14
|
-
columnType: string;
|
|
15
|
-
length: number | null;
|
|
16
|
-
nullable: boolean;
|
|
17
|
-
defaultValue: any;
|
|
18
|
-
comment: string | null;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* 索引信息接口(键为索引名,值为列名数组)
|
|
23
|
-
*/
|
|
24
|
-
export interface IndexInfo {
|
|
25
|
-
[indexName: string]: string[];
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
// ==================== 变更相关类型 ====================
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* 字段变更接口
|
|
32
|
-
*/
|
|
33
|
-
export interface FieldChange {
|
|
34
|
-
type: 'length' | 'datatype' | 'comment' | 'default';
|
|
35
|
-
current: any;
|
|
36
|
-
expected: any;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/**
|
|
40
|
-
* 索引操作接口
|
|
41
|
-
*/
|
|
42
|
-
export interface IndexAction {
|
|
43
|
-
action: 'create' | 'drop';
|
|
44
|
-
indexName: string;
|
|
45
|
-
fieldName: string;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
/**
|
|
49
|
-
* 表变更计划接口
|
|
50
|
-
*/
|
|
51
|
-
export interface TablePlan {
|
|
52
|
-
changed: boolean;
|
|
53
|
-
addClauses: string[];
|
|
54
|
-
modifyClauses: string[];
|
|
55
|
-
defaultClauses: string[];
|
|
56
|
-
indexActions: IndexAction[];
|
|
57
|
-
commentActions?: string[];
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
// ==================== 统计相关类型 ====================
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* 全局统计对象
|
|
64
|
-
*/
|
|
65
|
-
export interface GlobalStats {
|
|
66
|
-
processedTables: number;
|
|
67
|
-
createdTables: number;
|
|
68
|
-
modifiedTables: number;
|
|
69
|
-
addFields: number;
|
|
70
|
-
nameChanges: number;
|
|
71
|
-
typeChanges: number;
|
|
72
|
-
minChanges: number;
|
|
73
|
-
maxChanges: number;
|
|
74
|
-
defaultChanges: number;
|
|
75
|
-
indexCreate: number;
|
|
76
|
-
indexDrop: number;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// ==================== 解析相关类型 ====================
|
|
80
|
-
|
|
81
|
-
/**
|
|
82
|
-
* 解析后的字段规则
|
|
83
|
-
*/
|
|
84
|
-
export interface ParsedFieldRule {
|
|
85
|
-
name: string;
|
|
86
|
-
type: 'string' | 'number' | 'text' | 'array_string' | 'array_text';
|
|
87
|
-
min: number | null;
|
|
88
|
-
max: number | null;
|
|
89
|
-
default: any;
|
|
90
|
-
index: 0 | 1;
|
|
91
|
-
regex: string | null;
|
|
92
|
-
}
|
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* syncDb 数据库版本检查模块
|
|
3
|
-
*
|
|
4
|
-
* 包含:
|
|
5
|
-
* - 数据库版本验证(MySQL/PostgreSQL/SQLite)
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { Env } from '../../env.js';
|
|
9
|
-
import { Logger } from '../../lib/logger.js';
|
|
10
|
-
import { DB_VERSION_REQUIREMENTS, IS_MYSQL, IS_PG, IS_SQLITE } from './constants.js';
|
|
11
|
-
import type { SQL } from 'bun';
|
|
12
|
-
|
|
13
|
-
/**
|
|
14
|
-
* 数据库版本检查(按方言)
|
|
15
|
-
*
|
|
16
|
-
* 根据当前数据库类型检查版本是否符合最低要求:
|
|
17
|
-
* - MySQL: >= 8.0
|
|
18
|
-
* - PostgreSQL: >= 17
|
|
19
|
-
* - SQLite: >= 3.50.0
|
|
20
|
-
*
|
|
21
|
-
* @param sql - SQL 客户端实例
|
|
22
|
-
* @throws {Error} 如果数据库版本不符合要求或无法获取版本信息
|
|
23
|
-
*/
|
|
24
|
-
export async function ensureDbVersion(sql: SQL): Promise<void> {
|
|
25
|
-
if (!sql) throw new Error('SQL 客户端未初始化');
|
|
26
|
-
|
|
27
|
-
if (IS_MYSQL) {
|
|
28
|
-
const r = await sql`SELECT VERSION() AS version`;
|
|
29
|
-
if (!r || r.length === 0 || !r[0]?.version) {
|
|
30
|
-
throw new Error('无法获取 MySQL 版本信息');
|
|
31
|
-
}
|
|
32
|
-
const version = r[0].version;
|
|
33
|
-
const majorVersion = parseInt(String(version).split('.')[0], 10);
|
|
34
|
-
if (!Number.isFinite(majorVersion) || majorVersion < DB_VERSION_REQUIREMENTS.MYSQL_MIN_MAJOR) {
|
|
35
|
-
throw new Error(`此脚本仅支持 MySQL ${DB_VERSION_REQUIREMENTS.MYSQL_MIN_MAJOR}.0+,当前版本: ${version}`);
|
|
36
|
-
}
|
|
37
|
-
Logger.info(`MySQL 版本: ${version}`);
|
|
38
|
-
return;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
if (IS_PG) {
|
|
42
|
-
const r = await sql`SELECT version() AS version`;
|
|
43
|
-
if (!r || r.length === 0 || !r[0]?.version) {
|
|
44
|
-
throw new Error('无法获取 PostgreSQL 版本信息');
|
|
45
|
-
}
|
|
46
|
-
const versionText = r[0].version;
|
|
47
|
-
Logger.info(`PostgreSQL 版本: ${versionText}`);
|
|
48
|
-
const m = /PostgreSQL\s+(\d+)/i.exec(versionText);
|
|
49
|
-
const major = m ? parseInt(m[1], 10) : NaN;
|
|
50
|
-
if (!Number.isFinite(major) || major < DB_VERSION_REQUIREMENTS.POSTGRES_MIN_MAJOR) {
|
|
51
|
-
throw new Error(`此脚本要求 PostgreSQL >= ${DB_VERSION_REQUIREMENTS.POSTGRES_MIN_MAJOR},当前: ${versionText}`);
|
|
52
|
-
}
|
|
53
|
-
return;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
if (IS_SQLITE) {
|
|
57
|
-
const r = await sql`SELECT sqlite_version() AS version`;
|
|
58
|
-
if (!r || r.length === 0 || !r[0]?.version) {
|
|
59
|
-
throw new Error('无法获取 SQLite 版本信息');
|
|
60
|
-
}
|
|
61
|
-
const version = r[0].version;
|
|
62
|
-
Logger.info(`SQLite 版本: ${version}`);
|
|
63
|
-
// 强制最低版本:SQLite ≥ 3.50.0
|
|
64
|
-
const [maj, min, patch] = String(version)
|
|
65
|
-
.split('.')
|
|
66
|
-
.map((v) => parseInt(v, 10) || 0);
|
|
67
|
-
const vnum = maj * 10000 + min * 100 + patch; // 3.50.0 -> 35000
|
|
68
|
-
if (!Number.isFinite(vnum) || vnum < DB_VERSION_REQUIREMENTS.SQLITE_MIN_VERSION_NUM) {
|
|
69
|
-
throw new Error(`此脚本要求 SQLite >= ${DB_VERSION_REQUIREMENTS.SQLITE_MIN_VERSION},当前: ${version}`);
|
|
70
|
-
}
|
|
71
|
-
return;
|
|
72
|
-
}
|
|
73
|
-
}
|
package/commands/syncDb.ts
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SyncDb 命令 - 同步数据库表结构
|
|
3
|
-
*/
|
|
4
|
-
|
|
5
|
-
import { Command } from 'commander';
|
|
6
|
-
import { join } from 'pathe';
|
|
7
|
-
import { existsSync } from 'node:fs';
|
|
8
|
-
import { Logger } from '../lib/logger.js';
|
|
9
|
-
import { SyncDb, type SyncDbStats } from './syncDb/index.js';
|
|
10
|
-
|
|
11
|
-
interface SyncDbOptions {
|
|
12
|
-
table?: string;
|
|
13
|
-
dryRun: boolean;
|
|
14
|
-
}
|
|
15
|
-
|
|
16
|
-
export async function syncDbCommand(options: SyncDbOptions): Promise<SyncDbStats> {
|
|
17
|
-
try {
|
|
18
|
-
// 设置环境变量
|
|
19
|
-
if (options.dryRun) {
|
|
20
|
-
process.env.SYNC_DRY_RUN = '1';
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
if (options.table) {
|
|
24
|
-
process.env.SYNC_TABLE = options.table;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
// 执行同步并返回统计
|
|
28
|
-
const stats = await SyncDb();
|
|
29
|
-
return stats;
|
|
30
|
-
} catch (error: any) {
|
|
31
|
-
Logger.error('数据库同步失败:', error);
|
|
32
|
-
process.exit(1);
|
|
33
|
-
}
|
|
34
|
-
}
|
package/commands/syncDev.ts
DELETED
|
@@ -1,237 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* SyncDev 命令 - 同步开发者管理员到数据库
|
|
3
|
-
* - 邮箱: 通过 DEV_EMAIL 环境变量配置(默认 dev@qq.com)
|
|
4
|
-
* - 姓名: 开发者
|
|
5
|
-
* - 密码: 使用 bcrypt 加密,通过 DEV_PASSWORD 环境变量配置
|
|
6
|
-
* - 角色: roleCode=dev, roleType=admin
|
|
7
|
-
* - 表名: core_admin
|
|
8
|
-
*/
|
|
9
|
-
|
|
10
|
-
import { Logger } from '../lib/logger.js';
|
|
11
|
-
import { Cipher } from '../lib/cipher.js';
|
|
12
|
-
import { Database } from '../lib/database.js';
|
|
13
|
-
import { Env } from '../env.js';
|
|
14
|
-
|
|
15
|
-
interface SyncDevOptions {
|
|
16
|
-
plan?: boolean;
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
export interface SyncDevStats {
|
|
20
|
-
adminCount: number;
|
|
21
|
-
roleCount: number;
|
|
22
|
-
cachedRoles: number;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* SyncDev 命令主函数
|
|
27
|
-
*/
|
|
28
|
-
export async function syncDevCommand(options: SyncDevOptions = {}): Promise<SyncDevStats> {
|
|
29
|
-
try {
|
|
30
|
-
if (options.plan) {
|
|
31
|
-
Logger.info('[计划] 同步完成后将初始化/更新开发管理员账号(plan 模式不执行)');
|
|
32
|
-
return { adminCount: 0, roleCount: 0, cachedRoles: 0 };
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
if (!Env.DEV_PASSWORD) {
|
|
36
|
-
return { adminCount: 0, roleCount: 0, cachedRoles: 0 };
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// 连接数据库(SQL + Redis)
|
|
40
|
-
await Database.connect();
|
|
41
|
-
|
|
42
|
-
const helper = Database.getDbHelper();
|
|
43
|
-
|
|
44
|
-
// 检查 core_admin 表是否存在
|
|
45
|
-
const existAdmin = await helper.tableExists('core_admin');
|
|
46
|
-
if (!existAdmin) {
|
|
47
|
-
return { adminCount: 0, roleCount: 0, cachedRoles: 0 };
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// 检查 core_role 表是否存在
|
|
51
|
-
const existRole = await helper.tableExists('core_role');
|
|
52
|
-
if (!existRole) {
|
|
53
|
-
return { adminCount: 0, roleCount: 0, cachedRoles: 0 };
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// 检查 core_menu 表是否存在
|
|
57
|
-
const existMenu = await helper.tableExists('core_menu');
|
|
58
|
-
if (!existMenu) {
|
|
59
|
-
return { adminCount: 0, roleCount: 0, cachedRoles: 0 };
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// 查询所有菜单 ID
|
|
63
|
-
const allMenus = await helper.getAll({
|
|
64
|
-
table: 'core_menu',
|
|
65
|
-
fields: ['id']
|
|
66
|
-
});
|
|
67
|
-
|
|
68
|
-
if (!allMenus || !Array.isArray(allMenus)) {
|
|
69
|
-
return { adminCount: 0, roleCount: 0, cachedRoles: 0 };
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
const menuIds = allMenus.length > 0 ? allMenus.map((m: any) => m.id).join(',') : '';
|
|
73
|
-
|
|
74
|
-
// 查询所有接口 ID
|
|
75
|
-
const existApi = await helper.tableExists('core_api');
|
|
76
|
-
let apiIds = '';
|
|
77
|
-
if (existApi) {
|
|
78
|
-
const allApis = await helper.getAll({
|
|
79
|
-
table: 'core_api',
|
|
80
|
-
fields: ['id']
|
|
81
|
-
});
|
|
82
|
-
|
|
83
|
-
if (allApis && Array.isArray(allApis) && allApis.length > 0) {
|
|
84
|
-
apiIds = allApis.map((a: any) => a.id).join(',');
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// 查询或创建 dev 角色
|
|
89
|
-
let devRole = await helper.getOne({
|
|
90
|
-
table: 'core_role',
|
|
91
|
-
where: { code: 'dev' }
|
|
92
|
-
});
|
|
93
|
-
|
|
94
|
-
if (devRole) {
|
|
95
|
-
// 更新 dev 角色的菜单和接口权限
|
|
96
|
-
await helper.updData({
|
|
97
|
-
table: 'core_role',
|
|
98
|
-
where: { code: 'dev' },
|
|
99
|
-
data: {
|
|
100
|
-
name: '开发者角色',
|
|
101
|
-
description: '拥有所有菜单和接口权限的开发者角色',
|
|
102
|
-
menus: menuIds,
|
|
103
|
-
apis: apiIds
|
|
104
|
-
}
|
|
105
|
-
});
|
|
106
|
-
} else {
|
|
107
|
-
// 创建 dev 角色
|
|
108
|
-
const roleId = await helper.insData({
|
|
109
|
-
table: 'core_role',
|
|
110
|
-
data: {
|
|
111
|
-
name: '开发者角色',
|
|
112
|
-
code: 'dev',
|
|
113
|
-
description: '拥有所有菜单和接口权限的开发者角色',
|
|
114
|
-
menus: menuIds,
|
|
115
|
-
apis: apiIds,
|
|
116
|
-
sort: 0
|
|
117
|
-
}
|
|
118
|
-
});
|
|
119
|
-
devRole = { id: roleId };
|
|
120
|
-
Logger.info('dev 角色已创建');
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// 使用 bcrypt 加密密码
|
|
124
|
-
const hashed = await Cipher.hashPassword(Env.DEV_PASSWORD);
|
|
125
|
-
|
|
126
|
-
// 准备开发管理员数据
|
|
127
|
-
const devData = {
|
|
128
|
-
name: '开发者',
|
|
129
|
-
nickname: '开发者',
|
|
130
|
-
email: Env.DEV_EMAIL,
|
|
131
|
-
username: 'dev',
|
|
132
|
-
password: hashed,
|
|
133
|
-
roleId: devRole.id,
|
|
134
|
-
roleCode: 'dev',
|
|
135
|
-
roleType: 'admin'
|
|
136
|
-
};
|
|
137
|
-
|
|
138
|
-
// 查询现有账号
|
|
139
|
-
const existing = await helper.getOne({
|
|
140
|
-
table: 'core_admin',
|
|
141
|
-
where: { email: Env.DEV_EMAIL }
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
let isNew = false;
|
|
145
|
-
if (existing) {
|
|
146
|
-
// 更新现有账号
|
|
147
|
-
await helper.updData({
|
|
148
|
-
table: 'core_admin',
|
|
149
|
-
where: { email: Env.DEV_EMAIL },
|
|
150
|
-
data: devData
|
|
151
|
-
});
|
|
152
|
-
} else {
|
|
153
|
-
// 插入新账号
|
|
154
|
-
await helper.insData({
|
|
155
|
-
table: 'core_admin',
|
|
156
|
-
data: devData
|
|
157
|
-
});
|
|
158
|
-
isNew = true;
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// 缓存角色权限数据到 Redis
|
|
162
|
-
let cachedRolesCount = 0;
|
|
163
|
-
try {
|
|
164
|
-
// 检查必要的表是否存在
|
|
165
|
-
const apiTableExists = await helper.tableExists('core_api');
|
|
166
|
-
const roleTableExists = await helper.tableExists('core_role');
|
|
167
|
-
|
|
168
|
-
if (apiTableExists && roleTableExists) {
|
|
169
|
-
// 查询所有角色
|
|
170
|
-
const roles = await helper.getAll({
|
|
171
|
-
table: 'core_role',
|
|
172
|
-
fields: ['id', 'code', 'apis']
|
|
173
|
-
});
|
|
174
|
-
|
|
175
|
-
// 查询所有接口
|
|
176
|
-
const allApis = await helper.getAll({
|
|
177
|
-
table: 'core_api',
|
|
178
|
-
fields: ['id', 'name', 'path', 'method', 'description', 'addonName']
|
|
179
|
-
});
|
|
180
|
-
|
|
181
|
-
const redis = Database.getRedis();
|
|
182
|
-
|
|
183
|
-
// 为每个角色缓存接口权限
|
|
184
|
-
for (const role of roles) {
|
|
185
|
-
if (!role.apis) continue;
|
|
186
|
-
|
|
187
|
-
// 解析角色的接口 ID 列表
|
|
188
|
-
const apiIds = role.apis
|
|
189
|
-
.split(',')
|
|
190
|
-
.map((id: string) => parseInt(id.trim()))
|
|
191
|
-
.filter((id: number) => !isNaN(id));
|
|
192
|
-
|
|
193
|
-
// 根据 ID 过滤出接口路径
|
|
194
|
-
const roleApiPaths = allApis.filter((api: any) => apiIds.includes(api.id)).map((api: any) => `${api.method}${api.path}`);
|
|
195
|
-
|
|
196
|
-
if (roleApiPaths.length === 0) continue;
|
|
197
|
-
|
|
198
|
-
// 使用 Redis Set 缓存角色权限
|
|
199
|
-
const redisKey = `role:apis:${role.code}`;
|
|
200
|
-
|
|
201
|
-
// 先删除旧数据
|
|
202
|
-
await redis.del(redisKey);
|
|
203
|
-
|
|
204
|
-
// 批量添加到 Set(使用扩展运算符展开数组)
|
|
205
|
-
const result = await redis.sadd(redisKey, ...roleApiPaths);
|
|
206
|
-
|
|
207
|
-
if (result > 0) {
|
|
208
|
-
cachedRolesCount++;
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
}
|
|
212
|
-
} catch (error: any) {
|
|
213
|
-
// 忽略缓存错误
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
// 获取统计数据
|
|
217
|
-
const allAdmins = await helper.getAll({
|
|
218
|
-
table: 'core_admin',
|
|
219
|
-
fields: ['id']
|
|
220
|
-
});
|
|
221
|
-
const allRoles = await helper.getAll({
|
|
222
|
-
table: 'core_role',
|
|
223
|
-
fields: ['id']
|
|
224
|
-
});
|
|
225
|
-
|
|
226
|
-
return {
|
|
227
|
-
adminCount: allAdmins.length,
|
|
228
|
-
roleCount: allRoles.length,
|
|
229
|
-
cachedRoles: cachedRolesCount
|
|
230
|
-
};
|
|
231
|
-
} catch (error: any) {
|
|
232
|
-
Logger.error('开发管理员同步失败:', error);
|
|
233
|
-
process.exit(1);
|
|
234
|
-
} finally {
|
|
235
|
-
await Database?.disconnect();
|
|
236
|
-
}
|
|
237
|
-
}
|