befly 2.3.3 → 3.0.1
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/checks/conflict.ts +329 -0
- package/checks/table.ts +252 -0
- package/config/env.ts +218 -0
- package/config/fields.ts +55 -0
- package/config/regexAliases.ts +51 -0
- package/config/reserved.ts +96 -0
- package/main.ts +47 -0
- package/package.json +26 -11
- package/plugins/db.ts +60 -0
- package/plugins/logger.ts +28 -0
- package/plugins/redis.ts +47 -0
- package/scripts/syncDb/apply.ts +171 -0
- package/scripts/syncDb/constants.ts +71 -0
- package/scripts/syncDb/ddl.ts +189 -0
- package/scripts/syncDb/helpers.ts +173 -0
- package/scripts/syncDb/index.ts +203 -0
- package/scripts/syncDb/schema.ts +199 -0
- package/scripts/syncDb/sqlite.ts +50 -0
- package/scripts/syncDb/state.ts +106 -0
- package/scripts/syncDb/table.ts +214 -0
- package/scripts/syncDb/tableCreate.ts +148 -0
- package/scripts/syncDb/tests/constants.test.ts +105 -0
- package/scripts/syncDb/tests/ddl.test.ts +134 -0
- package/scripts/syncDb/tests/helpers.test.ts +70 -0
- package/scripts/syncDb/types.ts +92 -0
- package/scripts/syncDb/version.ts +73 -0
- package/scripts/syncDb.ts +10 -0
- package/tsconfig.json +58 -0
- package/types/addon.d.ts +53 -0
- package/types/api.d.ts +249 -0
- package/types/befly.d.ts +230 -0
- package/types/common.d.ts +215 -0
- package/types/context.d.ts +7 -0
- package/types/crypto.d.ts +23 -0
- package/types/database.d.ts +273 -0
- package/types/index.d.ts +450 -0
- package/types/index.ts +438 -0
- package/types/jwt.d.ts +99 -0
- package/types/logger.d.ts +43 -0
- package/types/plugin.d.ts +109 -0
- package/types/redis.d.ts +46 -0
- package/types/tool.d.ts +67 -0
- package/types/validator.d.ts +43 -0
- package/types/validator.ts +43 -0
- package/utils/colors.ts +221 -0
- package/utils/crypto.ts +308 -0
- package/utils/database.ts +348 -0
- package/utils/dbHelper.ts +713 -0
- package/utils/helper.ts +812 -0
- package/utils/index.ts +33 -0
- package/utils/jwt.ts +493 -0
- package/utils/logger.ts +191 -0
- package/utils/redisHelper.ts +321 -0
- package/utils/requestContext.ts +167 -0
- package/utils/sqlBuilder.ts +611 -0
- package/utils/validate.ts +493 -0
- package/utils/{xml.js → xml.ts} +100 -74
- package/.npmrc +0 -3
- package/.prettierignore +0 -2
- package/.prettierrc +0 -11
- package/apis/health/info.js +0 -49
- package/apis/tool/tokenCheck.js +0 -29
- package/bin/befly.js +0 -109
- package/bunfig.toml +0 -3
- package/checks/table.js +0 -206
- package/config/env.js +0 -64
- package/main.js +0 -579
- package/plugins/db.js +0 -46
- package/plugins/logger.js +0 -14
- package/plugins/redis.js +0 -32
- package/plugins/tool.js +0 -8
- package/scripts/syncDb.js +0 -752
- package/scripts/syncDev.js +0 -96
- package/system.js +0 -118
- package/tables/common.json +0 -16
- package/tables/tool.json +0 -6
- package/utils/api.js +0 -27
- package/utils/colors.js +0 -83
- package/utils/crypto.js +0 -260
- package/utils/index.js +0 -334
- package/utils/jwt.js +0 -387
- package/utils/logger.js +0 -143
- package/utils/redisHelper.js +0 -74
- package/utils/sqlBuilder.js +0 -498
- package/utils/sqlManager.js +0 -471
- package/utils/tool.js +0 -31
- package/utils/validate.js +0 -226
package/config/env.ts
ADDED
|
@@ -0,0 +1,218 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 环境变量配置 - TypeScript 版本
|
|
3
|
+
* 类型化所有环境变量
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 环境变量配置接口
|
|
8
|
+
*/
|
|
9
|
+
export interface EnvConfig {
|
|
10
|
+
// ========== 项目配置 ==========
|
|
11
|
+
/** 项目模式:development | production | test */
|
|
12
|
+
NODE_ENV: string;
|
|
13
|
+
/** 应用名称 */
|
|
14
|
+
APP_NAME: string;
|
|
15
|
+
/** MD5 加密盐 */
|
|
16
|
+
MD5_SALT: string;
|
|
17
|
+
/** 监听端口 */
|
|
18
|
+
APP_PORT: number;
|
|
19
|
+
/** 监听主机 */
|
|
20
|
+
APP_HOST: string;
|
|
21
|
+
/** 超级管理员密码 */
|
|
22
|
+
DEV_PASSWORD: string;
|
|
23
|
+
/** 请求体大小限制(字节) */
|
|
24
|
+
BODY_LIMIT: number;
|
|
25
|
+
/** 是否进行参数验证 */
|
|
26
|
+
PARAMS_CHECK: string;
|
|
27
|
+
|
|
28
|
+
// ========== 日志配置 ==========
|
|
29
|
+
/** debug日志开关:0 | 1 */
|
|
30
|
+
LOG_DEBUG: number;
|
|
31
|
+
/** 日志排除字段(逗号分隔) */
|
|
32
|
+
LOG_EXCLUDE_FIELDS: string;
|
|
33
|
+
/** 日志目录 */
|
|
34
|
+
LOG_DIR: string;
|
|
35
|
+
/** 是否输出到控制台:0 | 1 */
|
|
36
|
+
LOG_TO_CONSOLE: number;
|
|
37
|
+
/** 日志文件最大大小(字节) */
|
|
38
|
+
LOG_MAX_SIZE: number;
|
|
39
|
+
|
|
40
|
+
// ========== 时区配置 ==========
|
|
41
|
+
/** 时区:Asia/Shanghai */
|
|
42
|
+
TZ: string;
|
|
43
|
+
|
|
44
|
+
// ========== 数据库配置 ==========
|
|
45
|
+
/** 是否启用数据库:0 | 1 */
|
|
46
|
+
DB_ENABLE: number;
|
|
47
|
+
/** 数据库类型:sqlite | mysql | postgresql */
|
|
48
|
+
DB_TYPE: string;
|
|
49
|
+
/** 数据库主机 */
|
|
50
|
+
DB_HOST: string;
|
|
51
|
+
/** 数据库端口 */
|
|
52
|
+
DB_PORT: number;
|
|
53
|
+
/** 数据库用户名 */
|
|
54
|
+
DB_USER: string;
|
|
55
|
+
/** 数据库密码 */
|
|
56
|
+
DB_PASS: string;
|
|
57
|
+
/** 数据库名称 */
|
|
58
|
+
DB_NAME: string;
|
|
59
|
+
/** 是否启用调试:0 | 1 */
|
|
60
|
+
DB_DEBUG: number;
|
|
61
|
+
/** 连接池最大连接数 */
|
|
62
|
+
DB_POOL_MAX: number;
|
|
63
|
+
|
|
64
|
+
// ========== Redis 配置 ==========
|
|
65
|
+
/** 是否启用 Redis:0 | 1 */
|
|
66
|
+
REDIS_ENABLE: number;
|
|
67
|
+
/** Redis 主机 */
|
|
68
|
+
REDIS_HOST: string;
|
|
69
|
+
/** Redis 端口 */
|
|
70
|
+
REDIS_PORT: number;
|
|
71
|
+
/** Redis 用户名 */
|
|
72
|
+
REDIS_USERNAME: string;
|
|
73
|
+
/** Redis 密码 */
|
|
74
|
+
REDIS_PASSWORD: string;
|
|
75
|
+
/** Redis 数据库索引 */
|
|
76
|
+
REDIS_DB: number;
|
|
77
|
+
/** Redis 键前缀 */
|
|
78
|
+
REDIS_KEY_PREFIX: string;
|
|
79
|
+
|
|
80
|
+
// ========== JWT 配置 ==========
|
|
81
|
+
/** JWT 密钥 */
|
|
82
|
+
JWT_SECRET: string;
|
|
83
|
+
/** JWT 过期时间:7d | 30d | 1h */
|
|
84
|
+
JWT_EXPIRES_IN: string;
|
|
85
|
+
/** JWT 算法:HS256 | HS384 | HS512 */
|
|
86
|
+
JWT_ALGORITHM: string;
|
|
87
|
+
|
|
88
|
+
// ========== CORS 配置 ==========
|
|
89
|
+
/** 允许的来源 */
|
|
90
|
+
ALLOWED_ORIGIN: string;
|
|
91
|
+
/** 允许的方法 */
|
|
92
|
+
ALLOWED_METHODS: string;
|
|
93
|
+
/** 允许的头部 */
|
|
94
|
+
ALLOWED_HEADERS: string;
|
|
95
|
+
/** 暴露的头部 */
|
|
96
|
+
EXPOSE_HEADERS: string;
|
|
97
|
+
/** 预检请求缓存时间(秒) */
|
|
98
|
+
MAX_AGE: number;
|
|
99
|
+
/** 是否允许凭证 */
|
|
100
|
+
ALLOW_CREDENTIALS: string;
|
|
101
|
+
|
|
102
|
+
// ========== 邮件配置 ==========
|
|
103
|
+
/** 邮件服务器主机 */
|
|
104
|
+
MAIL_HOST: string;
|
|
105
|
+
/** 邮件服务器端口 */
|
|
106
|
+
MAIL_PORT: number;
|
|
107
|
+
/** 是否使用连接池 */
|
|
108
|
+
MAIL_POOL: string;
|
|
109
|
+
/** 是否使用 SSL */
|
|
110
|
+
MAIL_SECURE: string;
|
|
111
|
+
/** 邮件用户名 */
|
|
112
|
+
MAIL_USER: string;
|
|
113
|
+
/** 邮件密码 */
|
|
114
|
+
MAIL_PASS: string;
|
|
115
|
+
/** 发件人名称 */
|
|
116
|
+
MAIL_SENDER: string;
|
|
117
|
+
/** 发件人地址 */
|
|
118
|
+
MAIL_ADDRESS: string;
|
|
119
|
+
|
|
120
|
+
// ========== 同步脚本配置 ==========
|
|
121
|
+
/** 是否合并 ALTER 语句 */
|
|
122
|
+
SYNC_MERGE_ALTER: string;
|
|
123
|
+
/** 是否同步在线索引 */
|
|
124
|
+
SYNC_ONLINE_INDEX: string;
|
|
125
|
+
/** 是否禁止字段缩小 */
|
|
126
|
+
SYNC_DISALLOW_SHRINK: string;
|
|
127
|
+
/** 是否允许类型变更 */
|
|
128
|
+
SYNC_ALLOW_TYPE_CHANGE: string;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
/**
|
|
132
|
+
* 获取环境变量值(带默认值)
|
|
133
|
+
*/
|
|
134
|
+
const getEnv = (key: string, defaultValue: string = ''): string => {
|
|
135
|
+
return process.env[key] || defaultValue;
|
|
136
|
+
};
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* 获取数字类型环境变量
|
|
140
|
+
*/
|
|
141
|
+
const getEnvNumber = (key: string, defaultValue: number = 0): number => {
|
|
142
|
+
const value = process.env[key];
|
|
143
|
+
return value ? Number(value) : defaultValue;
|
|
144
|
+
};
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* 环境变量配置对象
|
|
148
|
+
*/
|
|
149
|
+
export const Env: EnvConfig = {
|
|
150
|
+
// ========== 项目配置 ==========
|
|
151
|
+
NODE_ENV: getEnv('NODE_ENV', 'development'),
|
|
152
|
+
APP_NAME: getEnv('APP_NAME', 'befly'),
|
|
153
|
+
MD5_SALT: getEnv('MD5_SALT', 'befly'),
|
|
154
|
+
APP_PORT: getEnvNumber('APP_PORT', 3000),
|
|
155
|
+
APP_HOST: getEnv('APP_HOST', '0.0.0.0'),
|
|
156
|
+
DEV_PASSWORD: getEnv('DEV_PASSWORD', ''),
|
|
157
|
+
BODY_LIMIT: getEnvNumber('BODY_LIMIT', 10485760), // 10MB
|
|
158
|
+
PARAMS_CHECK: getEnv('PARAMS_CHECK', 'true'),
|
|
159
|
+
|
|
160
|
+
// ========== 日志配置 ==========
|
|
161
|
+
LOG_DEBUG: getEnvNumber('LOG_DEBUG', 0),
|
|
162
|
+
LOG_EXCLUDE_FIELDS: getEnv('LOG_EXCLUDE_FIELDS', 'password,token,secret'),
|
|
163
|
+
LOG_DIR: getEnv('LOG_DIR', './logs'),
|
|
164
|
+
LOG_TO_CONSOLE: getEnvNumber('LOG_TO_CONSOLE', 1),
|
|
165
|
+
LOG_MAX_SIZE: getEnvNumber('LOG_MAX_SIZE', 10485760), // 10MB
|
|
166
|
+
|
|
167
|
+
// ========== 时区配置 ==========
|
|
168
|
+
TZ: getEnv('TZ', 'Asia/Shanghai'),
|
|
169
|
+
|
|
170
|
+
// ========== 数据库配置 ==========
|
|
171
|
+
DB_ENABLE: getEnvNumber('DB_ENABLE', 1),
|
|
172
|
+
DB_TYPE: getEnv('DB_TYPE', 'mysql'),
|
|
173
|
+
DB_HOST: getEnv('DB_HOST', 'localhost'),
|
|
174
|
+
DB_PORT: getEnvNumber('DB_PORT', 3306),
|
|
175
|
+
DB_USER: getEnv('DB_USER', 'root'),
|
|
176
|
+
DB_PASS: getEnv('DB_PASS', ''),
|
|
177
|
+
DB_NAME: getEnv('DB_NAME', 'befly'),
|
|
178
|
+
DB_DEBUG: getEnvNumber('DB_DEBUG', 0),
|
|
179
|
+
DB_POOL_MAX: getEnvNumber('DB_POOL_MAX', 10),
|
|
180
|
+
|
|
181
|
+
// ========== Redis 配置 ==========
|
|
182
|
+
REDIS_ENABLE: getEnvNumber('REDIS_ENABLE', 1),
|
|
183
|
+
REDIS_HOST: getEnv('REDIS_HOST', 'localhost'),
|
|
184
|
+
REDIS_PORT: getEnvNumber('REDIS_PORT', 6379),
|
|
185
|
+
REDIS_USERNAME: getEnv('REDIS_USERNAME', ''),
|
|
186
|
+
REDIS_PASSWORD: getEnv('REDIS_PASSWORD', ''),
|
|
187
|
+
REDIS_DB: getEnvNumber('REDIS_DB', 0),
|
|
188
|
+
REDIS_KEY_PREFIX: getEnv('REDIS_KEY_PREFIX', 'befly'),
|
|
189
|
+
|
|
190
|
+
// ========== JWT 配置 ==========
|
|
191
|
+
JWT_SECRET: getEnv('JWT_SECRET', 'befly-secret'),
|
|
192
|
+
JWT_EXPIRES_IN: getEnv('JWT_EXPIRES_IN', '7d'),
|
|
193
|
+
JWT_ALGORITHM: getEnv('JWT_ALGORITHM', 'HS256'),
|
|
194
|
+
|
|
195
|
+
// ========== CORS 配置 ==========
|
|
196
|
+
ALLOWED_ORIGIN: getEnv('ALLOWED_ORIGIN', '*'),
|
|
197
|
+
ALLOWED_METHODS: getEnv('ALLOWED_METHODS', 'GET, POST, PUT, DELETE, OPTIONS'),
|
|
198
|
+
ALLOWED_HEADERS: getEnv('ALLOWED_HEADERS', 'Content-Type, Authorization, authorization, token'),
|
|
199
|
+
EXPOSE_HEADERS: getEnv('EXPOSE_HEADERS', 'Content-Range, X-Content-Range, Authorization, authorization, token'),
|
|
200
|
+
MAX_AGE: getEnvNumber('MAX_AGE', 86400),
|
|
201
|
+
ALLOW_CREDENTIALS: getEnv('ALLOW_CREDENTIALS', 'true'),
|
|
202
|
+
|
|
203
|
+
// ========== 邮件配置 ==========
|
|
204
|
+
MAIL_HOST: getEnv('MAIL_HOST', ''),
|
|
205
|
+
MAIL_PORT: getEnvNumber('MAIL_PORT', 587),
|
|
206
|
+
MAIL_POOL: getEnv('MAIL_POOL', 'true'),
|
|
207
|
+
MAIL_SECURE: getEnv('MAIL_SECURE', 'false'),
|
|
208
|
+
MAIL_USER: getEnv('MAIL_USER', ''),
|
|
209
|
+
MAIL_PASS: getEnv('MAIL_PASS', ''),
|
|
210
|
+
MAIL_SENDER: getEnv('MAIL_SENDER', ''),
|
|
211
|
+
MAIL_ADDRESS: getEnv('MAIL_ADDRESS', ''),
|
|
212
|
+
|
|
213
|
+
// ========== 同步脚本配置 ==========
|
|
214
|
+
SYNC_MERGE_ALTER: getEnv('SYNC_MERGE_ALTER', 'false'),
|
|
215
|
+
SYNC_ONLINE_INDEX: getEnv('SYNC_ONLINE_INDEX', 'false'),
|
|
216
|
+
SYNC_DISALLOW_SHRINK: getEnv('SYNC_DISALLOW_SHRINK', 'true'),
|
|
217
|
+
SYNC_ALLOW_TYPE_CHANGE: getEnv('SYNC_ALLOW_TYPE_CHANGE', 'false')
|
|
218
|
+
};
|
package/config/fields.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 通用字段定义
|
|
3
|
+
*
|
|
4
|
+
* 用于在 API 和表定义中复用常见字段规则
|
|
5
|
+
*
|
|
6
|
+
* 格式:`字段标签|数据类型|最小值|最大值|默认值|是否必填|正则表达式`
|
|
7
|
+
*
|
|
8
|
+
* 说明:
|
|
9
|
+
* - 字段标签:用于显示的中文名称
|
|
10
|
+
* - 数据类型:string、number、boolean、array_string、array_text 等
|
|
11
|
+
* - 最小值:string 类型表示最小长度,number 类型表示最小数值,array 类型表示最小元素个数
|
|
12
|
+
* - 最大值:string 类型表示最大长度,number 类型表示最大数值,array 类型表示最大元素个数
|
|
13
|
+
* - 默认值:字段的默认值,无默认值时填 null
|
|
14
|
+
* - 是否必填:0 表示非必填,1 表示必填
|
|
15
|
+
* - 正则表达式:用于验证字段值的正则表达式,无验证时填 null
|
|
16
|
+
*
|
|
17
|
+
* 类型说明:
|
|
18
|
+
* - array_string: 短数组,存储为 VARCHAR,建议设置 max 限制(如 0-100)
|
|
19
|
+
* - array_text: 长数组,存储为 MEDIUMTEXT,min/max 可设为 null 表示不限制
|
|
20
|
+
*
|
|
21
|
+
* 正则表达式别名:
|
|
22
|
+
* - 使用 @ 前缀可以引用内置正则表达式别名,例如:
|
|
23
|
+
* - @number: 纯数字
|
|
24
|
+
* - @alphanumeric: 字母+数字
|
|
25
|
+
* - @email: 邮箱格式
|
|
26
|
+
* - @phone: 中国手机号
|
|
27
|
+
* - @chinese: 纯中文
|
|
28
|
+
* - 完整别名列表见 config/regexAliases.ts
|
|
29
|
+
*
|
|
30
|
+
* 示例:
|
|
31
|
+
* - '用户ID|array_text|null|null|null|0|@number' - 数字数组
|
|
32
|
+
* - '标签|array_string|0|50|null|0|@alphanumeric' - 字母数字数组
|
|
33
|
+
*/
|
|
34
|
+
|
|
35
|
+
export const Fields = {
|
|
36
|
+
_id: 'ID|number|1|null|null|1|null',
|
|
37
|
+
email: '邮箱|string|5|100|null|1|^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$',
|
|
38
|
+
phone: '手机号|string|11|11|null|1|^1[3-9]\\d{9}$',
|
|
39
|
+
page: '页码|number|1|9999|1|0|null',
|
|
40
|
+
limit: '每页数量|number|1|100|10|0|null',
|
|
41
|
+
title: '标题|string|1|200|null|0|null',
|
|
42
|
+
description: '描述|string|0|500|null|0|null',
|
|
43
|
+
keyword: '关键词|string|1|50|null|1|null',
|
|
44
|
+
keywords: '关键词列表|array_string|0|50|null|0|null',
|
|
45
|
+
enabled: '启用状态|number|0|1|1|0|^(0|1)$',
|
|
46
|
+
date: '日期|string|10|10|null|0|^\\d{4}-\\d{2}-\\d{2}$',
|
|
47
|
+
datetime: '日期时间|string|19|25|null|0|^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}',
|
|
48
|
+
filename: '文件名|string|1|255|null|0|null',
|
|
49
|
+
url: '网址|string|5|500|null|0|^https?://',
|
|
50
|
+
tag: '标签|array_string|0|10|null|0|null',
|
|
51
|
+
startTime: '开始时间|number|0|9999999999999|null|0|null',
|
|
52
|
+
endTime: '结束时间|number|0|9999999999999|null|0|null'
|
|
53
|
+
} as const;
|
|
54
|
+
|
|
55
|
+
export default Fields;
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 内置正则表达式别名
|
|
3
|
+
*
|
|
4
|
+
* 使用方式:在字段定义的 regex 位置使用 @别名 格式
|
|
5
|
+
* 例如:'字段名|array_text|null|null|null|0|@number'
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
export const RegexAliases = {
|
|
9
|
+
// 数字类型
|
|
10
|
+
number: '^\\d+$', // 纯数字
|
|
11
|
+
integer: '^-?\\d+$', // 整数(含负数)
|
|
12
|
+
float: '^-?\\d+(\\.\\d+)?$', // 浮点数
|
|
13
|
+
positive: '^[1-9]\\d*$', // 正整数(不含0)
|
|
14
|
+
|
|
15
|
+
// 字符串类型
|
|
16
|
+
word: '^[a-zA-Z]+$', // 纯字母
|
|
17
|
+
alphanumeric: '^[a-zA-Z0-9]+$', // 字母+数字
|
|
18
|
+
alphanumeric_: '^[a-zA-Z0-9_]+$', // 字母+数字+下划线
|
|
19
|
+
lowercase: '^[a-z]+$', // 小写字母
|
|
20
|
+
uppercase: '^[A-Z]+$', // 大写字母
|
|
21
|
+
|
|
22
|
+
// 中文
|
|
23
|
+
chinese: '^[\\u4e00-\\u9fa5]+$', // 纯中文
|
|
24
|
+
chinese_word: '^[\\u4e00-\\u9fa5a-zA-Z]+$', // 中文+字母
|
|
25
|
+
|
|
26
|
+
// 常用格式
|
|
27
|
+
email: '^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$', // 邮箱
|
|
28
|
+
phone: '^1[3-9]\\d{9}$', // 中国手机号
|
|
29
|
+
url: '^https?://', // URL
|
|
30
|
+
ip: '^((25[0-5]|2[0-4]\\d|[01]?\\d\\d?)\\.){3}(25[0-5]|2[0-4]\\d|[01]?\\d\\d?)$', // IPv4
|
|
31
|
+
|
|
32
|
+
// 特殊格式
|
|
33
|
+
uuid: '^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$', // UUID
|
|
34
|
+
hex: '^[0-9a-fA-F]+$', // 十六进制
|
|
35
|
+
base64: '^[A-Za-z0-9+/=]+$', // Base64
|
|
36
|
+
|
|
37
|
+
// 日期时间
|
|
38
|
+
date: '^\\d{4}-\\d{2}-\\d{2}$', // YYYY-MM-DD
|
|
39
|
+
time: '^\\d{2}:\\d{2}:\\d{2}$', // HH:MM:SS
|
|
40
|
+
datetime: '^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}', // ISO 8601
|
|
41
|
+
|
|
42
|
+
// 代码相关
|
|
43
|
+
variable: '^[a-zA-Z_][a-zA-Z0-9_]*$', // 变量名
|
|
44
|
+
constant: '^[A-Z][A-Z0-9_]*$', // 常量名(大写)
|
|
45
|
+
|
|
46
|
+
// 空值
|
|
47
|
+
empty: '^$', // 空字符串
|
|
48
|
+
notempty: '.+' // 非空
|
|
49
|
+
} as const;
|
|
50
|
+
|
|
51
|
+
export type RegexAliasName = keyof typeof RegexAliases;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 核心保留名称配置
|
|
3
|
+
* 定义框架保留的资源名称,防止用户和 addon 使用
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* 保留名称配置
|
|
8
|
+
*/
|
|
9
|
+
export const RESERVED_NAMES = {
|
|
10
|
+
/**
|
|
11
|
+
* 核心表前缀(禁止用户使用)
|
|
12
|
+
*/
|
|
13
|
+
tablePrefix: ['sys_'],
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* 核心 API 路由前缀(禁止用户使用)
|
|
17
|
+
*/
|
|
18
|
+
apiRoutes: ['/api/health', '/api/tool'],
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* 核心插件名(禁止用户使用)
|
|
22
|
+
*/
|
|
23
|
+
plugins: ['db', 'logger', 'redis', 'tool'],
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* 禁止用作 addon 名称
|
|
27
|
+
*/
|
|
28
|
+
addonNames: ['app', 'api']
|
|
29
|
+
} as const;
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* 检测表名是否使用了保留前缀
|
|
33
|
+
*/
|
|
34
|
+
export function isReservedTableName(tableName: string): boolean {
|
|
35
|
+
return RESERVED_NAMES.tablePrefix.some((prefix) => tableName.startsWith(prefix));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* 检测 API 路由是否使用了保留路径
|
|
40
|
+
*/
|
|
41
|
+
export function isReservedRoute(route: string): boolean {
|
|
42
|
+
// 移除方法前缀(如 POST/GET)
|
|
43
|
+
const path = route.replace(/^(GET|POST|PUT|DELETE|PATCH|OPTIONS|HEAD)\//i, '/');
|
|
44
|
+
return RESERVED_NAMES.apiRoutes.some((reserved) => path.startsWith(reserved));
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 检测插件名是否使用了保留名称
|
|
49
|
+
*/
|
|
50
|
+
export function isReservedPluginName(pluginName: string): boolean {
|
|
51
|
+
// 检测核心插件名
|
|
52
|
+
if (RESERVED_NAMES.plugins.includes(pluginName)) {
|
|
53
|
+
return true;
|
|
54
|
+
}
|
|
55
|
+
// 检测是否使用点号命名空间但前缀是保留名称
|
|
56
|
+
if (pluginName.includes('.')) {
|
|
57
|
+
const prefix = pluginName.split('.')[0];
|
|
58
|
+
return RESERVED_NAMES.plugins.includes(prefix);
|
|
59
|
+
}
|
|
60
|
+
return false;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* 检测 addon 名称是否使用了保留名称
|
|
65
|
+
*/
|
|
66
|
+
export function isReservedAddonName(addonName: string): boolean {
|
|
67
|
+
return RESERVED_NAMES.addonNames.includes(addonName.toLowerCase());
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* 获取保留前缀列表(用于错误提示)
|
|
72
|
+
*/
|
|
73
|
+
export function getReservedTablePrefixes(): string[] {
|
|
74
|
+
return [...RESERVED_NAMES.tablePrefix];
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* 获取保留路由列表(用于错误提示)
|
|
79
|
+
*/
|
|
80
|
+
export function getReservedRoutes(): string[] {
|
|
81
|
+
return [...RESERVED_NAMES.apiRoutes];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* 获取保留插件名列表(用于错误提示)
|
|
86
|
+
*/
|
|
87
|
+
export function getReservedPlugins(): string[] {
|
|
88
|
+
return [...RESERVED_NAMES.plugins];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* 获取保留 addon 名称列表(用于错误提示)
|
|
93
|
+
*/
|
|
94
|
+
export function getReservedAddonNames(): string[] {
|
|
95
|
+
return [...RESERVED_NAMES.addonNames];
|
|
96
|
+
}
|
package/main.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Befly 框架主入口文件
|
|
3
|
+
* 提供简洁的框架接口,核心逻辑已提取到 lifecycle 层
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Env } from './config/env.js';
|
|
7
|
+
import { Fields } from './config/fields.js';
|
|
8
|
+
import { Yes, No, cleanData } from './utils/index.js';
|
|
9
|
+
import { Logger } from './utils/logger.js';
|
|
10
|
+
import { Jwt } from './utils/jwt.js';
|
|
11
|
+
import { Validator } from './utils/validate.js';
|
|
12
|
+
import { Crypto2 } from './utils/crypto.js';
|
|
13
|
+
import { DbHelper } from './utils/dbHelper.js';
|
|
14
|
+
import { createSqlClient, getRedis, getSql, getDbHelper, initDatabase, closeDatabase } from './utils/database.js';
|
|
15
|
+
import { RedisHelper } from './utils/redisHelper.js';
|
|
16
|
+
import { Lifecycle } from './lifecycle/lifecycle.js';
|
|
17
|
+
|
|
18
|
+
import type { Server } from 'bun';
|
|
19
|
+
import type { BeflyContext, BeflyOptions } from './types/befly.js';
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Befly 框架核心类
|
|
23
|
+
* 职责:管理应用上下文和生命周期
|
|
24
|
+
*/
|
|
25
|
+
export class Befly {
|
|
26
|
+
/** 生命周期管理器 */
|
|
27
|
+
private lifecycle: Lifecycle;
|
|
28
|
+
|
|
29
|
+
/** 应用上下文 */
|
|
30
|
+
public appContext: BeflyContext;
|
|
31
|
+
|
|
32
|
+
constructor(options: BeflyOptions = {}) {
|
|
33
|
+
this.lifecycle = new Lifecycle(options);
|
|
34
|
+
this.appContext = {};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* 启动服务器
|
|
39
|
+
* @param callback - 启动完成后的回调函数
|
|
40
|
+
*/
|
|
41
|
+
async listen(callback?: (server: Server) => void): Promise<void> {
|
|
42
|
+
await this.lifecycle.start(this.appContext, callback);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// 核心类和工具导出(只导出运行时代码,不导出类型)
|
|
47
|
+
export { Env, Fields, Jwt, Validator, Crypto2, Logger, Yes, No, cleanData, DbHelper, createSqlClient, RedisHelper, getRedis, getSql, getDbHelper, initDatabase, closeDatabase };
|
package/package.json
CHANGED
|
@@ -1,42 +1,50 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "befly",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Befly - 为 Bun 专属打造的 API 接口框架核心引擎",
|
|
3
|
+
"version": "3.0.1",
|
|
4
|
+
"description": "Befly - 为 Bun 专属打造的 TypeScript API 接口框架核心引擎",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"private": false,
|
|
7
7
|
"publishConfig": {
|
|
8
8
|
"access": "public",
|
|
9
9
|
"registry": "https://registry.npmjs.org"
|
|
10
10
|
},
|
|
11
|
-
"main": "main.
|
|
11
|
+
"main": "main.ts",
|
|
12
|
+
"types": "./types/index.d.ts",
|
|
12
13
|
"exports": {
|
|
13
|
-
".":
|
|
14
|
+
".": {
|
|
15
|
+
"default": "./main.ts"
|
|
16
|
+
},
|
|
17
|
+
"./types": {
|
|
18
|
+
"types": "./types/index.d.ts",
|
|
19
|
+
"default": "./types/index.d.ts"
|
|
20
|
+
}
|
|
14
21
|
},
|
|
15
|
-
"
|
|
16
|
-
"
|
|
22
|
+
"scripts": {
|
|
23
|
+
"bundler": "bun build ./main.ts --outfile ./main.single.ts --minify --target bun"
|
|
17
24
|
},
|
|
18
|
-
"scripts": {},
|
|
19
25
|
"keywords": [
|
|
20
26
|
"bun",
|
|
21
27
|
"api",
|
|
22
28
|
"framework",
|
|
23
29
|
"core",
|
|
30
|
+
"typescript",
|
|
24
31
|
"javascript",
|
|
25
32
|
"backend",
|
|
26
33
|
"rest",
|
|
27
|
-
"http"
|
|
34
|
+
"http",
|
|
35
|
+
"type-safe"
|
|
28
36
|
],
|
|
29
37
|
"author": "chensuiyi <bimostyle@qq.com>",
|
|
30
38
|
"homepage": "https://chensuiyi.me",
|
|
31
39
|
"license": "Apache-2.0",
|
|
32
40
|
"files": [
|
|
33
|
-
"bin/",
|
|
34
41
|
"apis/",
|
|
35
42
|
"checks/",
|
|
36
43
|
"config/",
|
|
37
44
|
"plugins/",
|
|
38
45
|
"scripts/",
|
|
39
46
|
"tables/",
|
|
47
|
+
"types/",
|
|
40
48
|
"utils/",
|
|
41
49
|
".gitignore",
|
|
42
50
|
".npmignore",
|
|
@@ -46,10 +54,17 @@
|
|
|
46
54
|
".prettierignore",
|
|
47
55
|
".prettierrc",
|
|
48
56
|
"bunfig.toml",
|
|
57
|
+
"tsconfig.json",
|
|
49
58
|
"LICENSE",
|
|
59
|
+
"main.ts",
|
|
50
60
|
"main.js",
|
|
61
|
+
"system.ts",
|
|
51
62
|
"system.js",
|
|
52
63
|
"package.json",
|
|
53
64
|
"README.md"
|
|
54
|
-
]
|
|
55
|
-
|
|
65
|
+
],
|
|
66
|
+
"engines": {
|
|
67
|
+
"bun": ">=1.3.0"
|
|
68
|
+
},
|
|
69
|
+
"gitHead": "f96bac1a1c829e265284a6f619da680fffd1a075"
|
|
70
|
+
}
|
package/plugins/db.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 数据库插件 - TypeScript 版本
|
|
3
|
+
* 初始化数据库连接和 SQL 管理器
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Env } from '../config/env.js';
|
|
7
|
+
import { Logger } from '../utils/logger.js';
|
|
8
|
+
import { createSqlClient } from '../utils/database.js';
|
|
9
|
+
import { DbHelper } from '../utils/dbHelper.js';
|
|
10
|
+
import type { Plugin } from '../types/plugin.js';
|
|
11
|
+
import type { BeflyContext } from '../types/befly.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* 数据库插件
|
|
15
|
+
*/
|
|
16
|
+
const dbPlugin: Plugin = {
|
|
17
|
+
name: '_db',
|
|
18
|
+
after: ['_redis'],
|
|
19
|
+
|
|
20
|
+
async onInit(befly: BeflyContext): Promise<DbHelper | Record<string, never>> {
|
|
21
|
+
let sql: any = null;
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
if (Env.DB_ENABLE === 1) {
|
|
25
|
+
// 创建 Bun SQL 客户端(内置连接池),并确保连接验证成功后再继续
|
|
26
|
+
// 从环境变量读取连接超时配置
|
|
27
|
+
const connectionTimeout = process.env.DB_CONNECTION_TIMEOUT ? parseInt(process.env.DB_CONNECTION_TIMEOUT) : 5000;
|
|
28
|
+
|
|
29
|
+
sql = await createSqlClient({
|
|
30
|
+
connectionTimeout
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
// 创建数据库管理器实例,直接传入 sql 对象
|
|
34
|
+
const dbManager = new DbHelper(befly, sql);
|
|
35
|
+
|
|
36
|
+
Logger.info('数据库插件初始化成功');
|
|
37
|
+
return dbManager;
|
|
38
|
+
} else {
|
|
39
|
+
Logger.warn('数据库未启用(DB_ENABLE≠1),跳过初始化');
|
|
40
|
+
return {};
|
|
41
|
+
}
|
|
42
|
+
} catch (error: any) {
|
|
43
|
+
Logger.error('数据库初始化失败', error);
|
|
44
|
+
|
|
45
|
+
// 清理资源
|
|
46
|
+
if (sql) {
|
|
47
|
+
try {
|
|
48
|
+
await sql.close();
|
|
49
|
+
} catch (cleanupError: any) {
|
|
50
|
+
Logger.error('清理连接池失败:', cleanupError);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// 插件内禁止直接退出进程,抛出异常交由主流程统一处理
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export default dbPlugin;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 日志插件 - TypeScript 版本
|
|
3
|
+
* 提供全局日志功能
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Logger } from '../utils/logger.js';
|
|
7
|
+
import type { Plugin } from '../types/plugin.js';
|
|
8
|
+
import type { BeflyContext } from '../types/befly.js';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 日志插件
|
|
12
|
+
*/
|
|
13
|
+
const loggerPlugin: Plugin = {
|
|
14
|
+
name: '_logger',
|
|
15
|
+
after: [],
|
|
16
|
+
|
|
17
|
+
async onInit(befly: BeflyContext): Promise<typeof Logger> {
|
|
18
|
+
try {
|
|
19
|
+
Logger.info('日志插件初始化成功');
|
|
20
|
+
return Logger;
|
|
21
|
+
} catch (error: any) {
|
|
22
|
+
// 插件内禁止直接退出进程,抛出异常交由主流程统一处理
|
|
23
|
+
throw error;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
export default loggerPlugin;
|
package/plugins/redis.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Redis 插件 - TypeScript 版本
|
|
3
|
+
* 初始化 Redis 连接和助手工具
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { Env } from '../config/env.js';
|
|
7
|
+
import { Logger } from '../utils/logger.js';
|
|
8
|
+
import { RedisHelper } from '../utils/redisHelper.js';
|
|
9
|
+
import { initRedisOnly } from '../utils/database.js';
|
|
10
|
+
import type { Plugin } from '../types/plugin.js';
|
|
11
|
+
import type { BeflyContext } from '../types/befly.js';
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Redis 插件
|
|
15
|
+
*/
|
|
16
|
+
const redisPlugin: Plugin = {
|
|
17
|
+
name: '_redis',
|
|
18
|
+
after: ['_logger'],
|
|
19
|
+
|
|
20
|
+
async onInit(befly: BeflyContext): Promise<typeof RedisHelper | Record<string, never>> {
|
|
21
|
+
try {
|
|
22
|
+
if (Env.REDIS_ENABLE === 1) {
|
|
23
|
+
// 初始化 Redis 客户端(统一使用 database.ts 的连接管理)
|
|
24
|
+
await initRedisOnly();
|
|
25
|
+
|
|
26
|
+
Logger.info('Redis 插件初始化成功', {
|
|
27
|
+
host: Env.REDIS_HOST,
|
|
28
|
+
port: Env.REDIS_PORT,
|
|
29
|
+
db: Env.REDIS_DB
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
// 返回工具对象,向下游以相同 API 暴露
|
|
33
|
+
return RedisHelper;
|
|
34
|
+
} else {
|
|
35
|
+
Logger.warn('Redis 未启用,跳过初始化');
|
|
36
|
+
return {};
|
|
37
|
+
}
|
|
38
|
+
} catch (error: any) {
|
|
39
|
+
Logger.error('Redis 初始化失败', error);
|
|
40
|
+
|
|
41
|
+
// 插件内禁止直接退出进程,抛出异常交由主流程统一处理
|
|
42
|
+
throw error;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
export default redisPlugin;
|