befly 3.2.1 → 3.3.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/bin/index.ts +138 -0
- package/checks/conflict.ts +35 -25
- package/checks/table.ts +6 -6
- package/commands/addon.ts +57 -0
- package/commands/build.ts +74 -0
- package/commands/dev.ts +94 -0
- package/commands/index.ts +252 -0
- package/commands/script.ts +308 -0
- package/commands/start.ts +80 -0
- package/commands/syncApi.ts +328 -0
- package/{scripts → commands}/syncDb/apply.ts +2 -2
- package/{scripts → commands}/syncDb/constants.ts +13 -7
- package/{scripts → commands}/syncDb/ddl.ts +7 -5
- package/{scripts → commands}/syncDb/helpers.ts +18 -18
- package/{scripts → commands}/syncDb/index.ts +37 -23
- package/{scripts → commands}/syncDb/sqlite.ts +1 -1
- package/{scripts → commands}/syncDb/state.ts +10 -4
- package/{scripts → commands}/syncDb/table.ts +7 -7
- package/{scripts → commands}/syncDb/tableCreate.ts +7 -6
- package/{scripts → commands}/syncDb/types.ts +5 -5
- package/{scripts → commands}/syncDb/version.ts +1 -1
- package/commands/syncDb.ts +35 -0
- package/commands/syncDev.ts +174 -0
- package/commands/syncMenu.ts +368 -0
- package/config/env.ts +4 -4
- package/config/menu.json +67 -0
- package/{utils/crypto.ts → lib/cipher.ts} +16 -67
- package/lib/database.ts +296 -0
- package/{utils → lib}/dbHelper.ts +102 -56
- package/{utils → lib}/jwt.ts +124 -151
- package/{utils → lib}/logger.ts +47 -24
- package/lib/middleware.ts +271 -0
- package/{utils → lib}/redisHelper.ts +4 -4
- package/{utils/validate.ts → lib/validator.ts} +101 -78
- package/lifecycle/bootstrap.ts +63 -0
- package/lifecycle/checker.ts +165 -0
- package/lifecycle/cluster.ts +241 -0
- package/lifecycle/lifecycle.ts +139 -0
- package/lifecycle/loader.ts +513 -0
- package/main.ts +14 -12
- package/package.json +21 -9
- package/paths.ts +34 -0
- package/plugins/cache.ts +187 -0
- package/plugins/db.ts +4 -4
- package/plugins/logger.ts +1 -1
- package/plugins/redis.ts +4 -4
- package/router/api.ts +155 -0
- package/router/root.ts +53 -0
- package/router/static.ts +76 -0
- package/types/api.d.ts +0 -36
- package/types/befly.d.ts +8 -6
- package/types/common.d.ts +1 -1
- package/types/context.d.ts +3 -3
- package/types/util.d.ts +45 -0
- package/util.ts +301 -0
- package/config/fields.ts +0 -55
- package/config/regexAliases.ts +0 -51
- package/config/reserved.ts +0 -96
- package/scripts/syncDb/tests/constants.test.ts +0 -105
- package/scripts/syncDb/tests/ddl.test.ts +0 -134
- package/scripts/syncDb/tests/helpers.test.ts +0 -70
- package/scripts/syncDb.ts +0 -10
- package/types/index.d.ts +0 -450
- package/types/index.ts +0 -438
- package/types/validator.ts +0 -43
- package/utils/colors.ts +0 -221
- package/utils/database.ts +0 -348
- package/utils/helper.ts +0 -812
- package/utils/index.ts +0 -33
- package/utils/requestContext.ts +0 -167
- /package/{scripts → commands}/syncDb/schema.ts +0 -0
- /package/{utils → lib}/sqlBuilder.ts +0 -0
- /package/{utils → lib}/xml.ts +0 -0
|
@@ -7,9 +7,9 @@
|
|
|
7
7
|
* - 应用变更计划
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
10
|
+
import { snakeCase } from 'es-toolkit/string';
|
|
11
|
+
import { parseRule } from '../../util.js';
|
|
12
|
+
import { Logger } from '../../lib/logger.js';
|
|
13
13
|
import { IS_MYSQL, IS_PG, IS_SQLITE, SYSTEM_INDEX_FIELDS, CHANGE_TYPE_LABELS, typeMapping } from './constants.js';
|
|
14
14
|
import { quoteIdentifier, logFieldChange, resolveDefaultValue, generateDefaultSql, isStringOrArrayType, getSqlType } from './helpers.js';
|
|
15
15
|
import { buildIndexSQL, generateDDLClause, isPgCompatibleTypeChange } from './ddl.js';
|
|
@@ -58,7 +58,7 @@ export async function modifyTable(sql: SQL, tableName: string, fields: Record<st
|
|
|
58
58
|
|
|
59
59
|
for (const [fieldKey, fieldRule] of Object.entries(fields)) {
|
|
60
60
|
// 转换字段名为下划线格式(用于与数据库字段对比)
|
|
61
|
-
const dbFieldName =
|
|
61
|
+
const dbFieldName = snakeCase(fieldKey);
|
|
62
62
|
|
|
63
63
|
if (existingColumns[dbFieldName]) {
|
|
64
64
|
const comparison = compareFieldDefinition(existingColumns[dbFieldName], fieldRule, dbFieldName);
|
|
@@ -145,7 +145,7 @@ export async function modifyTable(sql: SQL, tableName: string, fields: Record<st
|
|
|
145
145
|
const parsed = parseRule(fieldRule);
|
|
146
146
|
const { name: fieldName, type: fieldType, max: fieldMax, default: fieldDefault } = parsed;
|
|
147
147
|
const lenPart = isStringOrArrayType(fieldType) ? ` 长度:${parseInt(String(fieldMax))}` : '';
|
|
148
|
-
Logger.info(`
|
|
148
|
+
Logger.info(` + 新增字段 ${dbFieldName} (${fieldType}${lenPart})`);
|
|
149
149
|
addClauses.push(generateDDLClause(fieldKey, fieldRule, true));
|
|
150
150
|
changed = true;
|
|
151
151
|
globalCount.addFields++;
|
|
@@ -165,7 +165,7 @@ export async function modifyTable(sql: SQL, tableName: string, fields: Record<st
|
|
|
165
165
|
// 检查业务字段索引
|
|
166
166
|
for (const [fieldKey, fieldRule] of Object.entries(fields)) {
|
|
167
167
|
// 转换字段名为下划线格式
|
|
168
|
-
const dbFieldName =
|
|
168
|
+
const dbFieldName = snakeCase(fieldKey);
|
|
169
169
|
|
|
170
170
|
const parsed = parseRule(fieldRule);
|
|
171
171
|
const indexName = `idx_${dbFieldName}`;
|
|
@@ -185,7 +185,7 @@ export async function modifyTable(sql: SQL, tableName: string, fields: Record<st
|
|
|
185
185
|
if (IS_PG) {
|
|
186
186
|
for (const [fieldKey, fieldRule] of Object.entries(fields)) {
|
|
187
187
|
// 转换字段名为下划线格式
|
|
188
|
-
const dbFieldName =
|
|
188
|
+
const dbFieldName = snakeCase(fieldKey);
|
|
189
189
|
|
|
190
190
|
if (existingColumns[dbFieldName]) {
|
|
191
191
|
const parsed = parseRule(fieldRule);
|
|
@@ -8,12 +8,13 @@
|
|
|
8
8
|
*
|
|
9
9
|
* 注意:此模块从 table.ts 中提取,用于解除循环依赖
|
|
10
10
|
*/
|
|
11
|
-
|
|
12
|
-
import {
|
|
11
|
+
import { snakeCase } from 'es-toolkit/string';
|
|
12
|
+
import { parseRule } from '../../util.js';
|
|
13
|
+
import { Logger } from '../../lib/logger.js';
|
|
13
14
|
import { IS_MYSQL, IS_PG, MYSQL_TABLE_CONFIG } from './constants.js';
|
|
14
15
|
import { quoteIdentifier } from './helpers.js';
|
|
15
16
|
import { buildSystemColumnDefs, buildBusinessColumnDefs, buildIndexSQL } from './ddl.js';
|
|
16
|
-
|
|
17
|
+
|
|
17
18
|
import type { SQL } from 'bun';
|
|
18
19
|
|
|
19
20
|
// 是否为计划模式(从环境变量读取)
|
|
@@ -48,7 +49,7 @@ async function addPostgresComments(sql: SQL, tableName: string, fields: Record<s
|
|
|
48
49
|
// 业务字段注释
|
|
49
50
|
for (const [fieldKey, fieldRule] of Object.entries(fields)) {
|
|
50
51
|
// 转换字段名为下划线格式
|
|
51
|
-
const dbFieldName =
|
|
52
|
+
const dbFieldName = snakeCase(fieldKey);
|
|
52
53
|
|
|
53
54
|
const parsed = parseRule(fieldRule);
|
|
54
55
|
const { name: fieldName } = parsed;
|
|
@@ -85,7 +86,7 @@ async function createTableIndexes(sql: SQL, tableName: string, fields: Record<st
|
|
|
85
86
|
// 业务字段索引
|
|
86
87
|
for (const [fieldKey, fieldRule] of Object.entries(fields)) {
|
|
87
88
|
// 转换字段名为下划线格式
|
|
88
|
-
const dbFieldName =
|
|
89
|
+
const dbFieldName = snakeCase(fieldKey);
|
|
89
90
|
|
|
90
91
|
const parsed = parseRule(fieldRule);
|
|
91
92
|
if (parsed.index === 1) {
|
|
@@ -132,7 +133,7 @@ export async function createTable(sql: SQL, tableName: string, fields: Record<st
|
|
|
132
133
|
Logger.info(`[计划] ${createSQL.replace(/\n+/g, ' ')}`);
|
|
133
134
|
} else {
|
|
134
135
|
await sql.unsafe(createSQL);
|
|
135
|
-
Logger.info(`
|
|
136
|
+
Logger.info(` ✓ 新建表`);
|
|
136
137
|
}
|
|
137
138
|
|
|
138
139
|
// PostgreSQL: 添加列注释
|
|
@@ -83,10 +83,10 @@ export interface GlobalStats {
|
|
|
83
83
|
*/
|
|
84
84
|
export interface ParsedFieldRule {
|
|
85
85
|
name: string;
|
|
86
|
-
type: string;
|
|
87
|
-
min:
|
|
88
|
-
max:
|
|
86
|
+
type: 'string' | 'number' | 'text' | 'array_string' | 'array_text';
|
|
87
|
+
min: number | null;
|
|
88
|
+
max: number | null;
|
|
89
89
|
default: any;
|
|
90
|
-
index:
|
|
91
|
-
regex: string;
|
|
90
|
+
index: 0 | 1;
|
|
91
|
+
regex: string | null;
|
|
92
92
|
}
|
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
* - 数据库版本验证(MySQL/PostgreSQL/SQLite)
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { Logger } from '../../utils/logger.js';
|
|
9
8
|
import { Env } from '../../config/env.js';
|
|
9
|
+
import { Logger } from '../../lib/logger.js';
|
|
10
10
|
import { DB_VERSION_REQUIREMENTS, IS_MYSQL, IS_PG, IS_SQLITE } from './constants.js';
|
|
11
11
|
import type { SQL } from 'bun';
|
|
12
12
|
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* SyncDb 命令 - 同步数据库表结构
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { Command } from 'commander';
|
|
6
|
+
import { join } from 'pathe';
|
|
7
|
+
import { existsSync } from 'node:fs';
|
|
8
|
+
import ora from 'ora';
|
|
9
|
+
import { Logger } from '../lib/logger.js';
|
|
10
|
+
import { SyncDb } from './syncDb/index.js';
|
|
11
|
+
|
|
12
|
+
interface SyncDbOptions {
|
|
13
|
+
table?: string;
|
|
14
|
+
dryRun: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export async function syncDbCommand(options: SyncDbOptions) {
|
|
18
|
+
try {
|
|
19
|
+
// 设置环境变量
|
|
20
|
+
if (options.dryRun) {
|
|
21
|
+
process.env.SYNC_DRY_RUN = '1';
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
if (options.table) {
|
|
25
|
+
process.env.SYNC_TABLE = options.table;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// 执行同步
|
|
29
|
+
await SyncDb();
|
|
30
|
+
Logger.info('数据库表结构同步完成');
|
|
31
|
+
} catch (error: any) {
|
|
32
|
+
Logger.error('数据库同步失败:', error);
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
@@ -0,0 +1,174 @@
|
|
|
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 '../config/env.js';
|
|
14
|
+
|
|
15
|
+
interface SyncDevOptions {
|
|
16
|
+
plan?: boolean;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* SyncDev 命令主函数
|
|
21
|
+
*/
|
|
22
|
+
export async function syncDevCommand(options: SyncDevOptions = {}) {
|
|
23
|
+
try {
|
|
24
|
+
if (options.plan) {
|
|
25
|
+
Logger.info('[计划] 同步完成后将初始化/更新开发管理员账号(plan 模式不执行)');
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (!Env.DEV_PASSWORD) {
|
|
30
|
+
Logger.warn('跳过开发管理员初始化:缺少 DEV_PASSWORD 配置');
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
Logger.info('开始同步开发管理员账号...\n');
|
|
35
|
+
|
|
36
|
+
// 连接数据库(SQL + Redis)
|
|
37
|
+
await Database.connect();
|
|
38
|
+
|
|
39
|
+
const helper = Database.getDbHelper();
|
|
40
|
+
|
|
41
|
+
// 检查 core_admin 表是否存在
|
|
42
|
+
const existAdmin = await helper.tableExists('core_admin');
|
|
43
|
+
if (!existAdmin) {
|
|
44
|
+
Logger.warn('跳过开发管理员初始化:未检测到 core_admin 表');
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 检查 core_role 表是否存在
|
|
49
|
+
const existRole = await helper.tableExists('core_role');
|
|
50
|
+
if (!existRole) {
|
|
51
|
+
Logger.warn('跳过开发管理员初始化:未检测到 core_role 表');
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// 检查 core_menu 表是否存在
|
|
56
|
+
const existMenu = await helper.tableExists('core_menu');
|
|
57
|
+
if (!existMenu) {
|
|
58
|
+
Logger.warn('跳过开发管理员初始化:未检测到 core_menu 表');
|
|
59
|
+
return;
|
|
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
|
+
Logger.warn('查询菜单失败或菜单表为空');
|
|
70
|
+
return;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const menuIds = allMenus.length > 0 ? allMenus.map((m: any) => m.id).join(',') : '';
|
|
74
|
+
Logger.info(`查询到 ${allMenus.length} 个菜单,ID 列表: ${menuIds || '(空)'}`);
|
|
75
|
+
|
|
76
|
+
// 查询所有接口 ID
|
|
77
|
+
const existApi = await helper.tableExists('core_api');
|
|
78
|
+
let apiIds = '';
|
|
79
|
+
if (existApi) {
|
|
80
|
+
const allApis = await helper.getAll({
|
|
81
|
+
table: 'core_api',
|
|
82
|
+
fields: ['id']
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
if (allApis && Array.isArray(allApis) && allApis.length > 0) {
|
|
86
|
+
apiIds = allApis.map((a: any) => a.id).join(',');
|
|
87
|
+
Logger.info(`查询到 ${allApis.length} 个接口,ID 列表: ${apiIds}`);
|
|
88
|
+
} else {
|
|
89
|
+
Logger.info('未查询到接口数据');
|
|
90
|
+
}
|
|
91
|
+
} else {
|
|
92
|
+
Logger.info('接口表不存在,跳过接口权限配置');
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// 查询或创建 dev 角色
|
|
96
|
+
let devRole = await helper.getOne({
|
|
97
|
+
table: 'core_role',
|
|
98
|
+
where: { code: 'dev' }
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
if (devRole) {
|
|
102
|
+
// 更新 dev 角色的菜单和接口权限
|
|
103
|
+
await helper.updData({
|
|
104
|
+
table: 'core_role',
|
|
105
|
+
where: { code: 'dev' },
|
|
106
|
+
data: {
|
|
107
|
+
name: '开发者角色',
|
|
108
|
+
description: '拥有所有菜单和接口权限的开发者角色',
|
|
109
|
+
menus: menuIds,
|
|
110
|
+
apis: apiIds
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
Logger.info('dev 角色菜单和接口权限已更新');
|
|
114
|
+
} else {
|
|
115
|
+
// 创建 dev 角色
|
|
116
|
+
const roleId = await helper.insData({
|
|
117
|
+
table: 'core_role',
|
|
118
|
+
data: {
|
|
119
|
+
name: '开发者角色',
|
|
120
|
+
code: 'dev',
|
|
121
|
+
description: '拥有所有菜单和接口权限的开发者角色',
|
|
122
|
+
menus: menuIds,
|
|
123
|
+
apis: apiIds,
|
|
124
|
+
sort: 0
|
|
125
|
+
}
|
|
126
|
+
});
|
|
127
|
+
devRole = { id: roleId };
|
|
128
|
+
Logger.info('dev 角色已创建');
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// 使用 bcrypt 加密密码
|
|
132
|
+
const hashed = await Cipher.hashPassword(Env.DEV_PASSWORD);
|
|
133
|
+
|
|
134
|
+
// 准备开发管理员数据
|
|
135
|
+
const devData = {
|
|
136
|
+
name: '开发者',
|
|
137
|
+
nickname: '开发者',
|
|
138
|
+
email: Env.DEV_EMAIL,
|
|
139
|
+
username: 'dev',
|
|
140
|
+
password: hashed,
|
|
141
|
+
roleId: devRole.id,
|
|
142
|
+
roleCode: 'dev',
|
|
143
|
+
roleType: 'admin'
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
// 查询现有账号
|
|
147
|
+
const existing = await helper.getOne({
|
|
148
|
+
table: 'core_admin',
|
|
149
|
+
where: { email: Env.DEV_EMAIL }
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
if (existing) {
|
|
153
|
+
// 更新现有账号
|
|
154
|
+
await helper.updData({
|
|
155
|
+
table: 'core_admin',
|
|
156
|
+
where: { email: Env.DEV_EMAIL },
|
|
157
|
+
data: devData
|
|
158
|
+
});
|
|
159
|
+
Logger.info(`✅ 开发管理员已更新:email=${Env.DEV_EMAIL}, username=dev, roleCode=dev, roleType=admin`);
|
|
160
|
+
} else {
|
|
161
|
+
// 插入新账号
|
|
162
|
+
await helper.insData({
|
|
163
|
+
table: 'core_admin',
|
|
164
|
+
data: devData
|
|
165
|
+
});
|
|
166
|
+
Logger.info(`✅ 开发管理员已初始化:email=${Env.DEV_EMAIL}, username=dev, roleCode=dev, roleType=admin`);
|
|
167
|
+
}
|
|
168
|
+
} catch (error: any) {
|
|
169
|
+
Logger.error('开发管理员同步失败:', error);
|
|
170
|
+
process.exit(1);
|
|
171
|
+
} finally {
|
|
172
|
+
await Database?.disconnect();
|
|
173
|
+
}
|
|
174
|
+
}
|