befly 3.8.30 → 3.8.32
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 +83 -0
- package/{config.ts → befly.config.ts} +26 -6
- package/checks/checkApp.ts +31 -1
- package/hooks/cors.ts +3 -3
- package/hooks/parser.ts +3 -3
- package/hooks/validator.ts +1 -1
- package/lib/cacheHelper.ts +0 -6
- package/lib/cipher.ts +2 -1
- package/lib/connect.ts +17 -19
- package/lib/jwt.ts +1 -1
- package/lib/logger.ts +1 -1
- package/lib/validator.ts +149 -384
- package/loader/loadHooks.ts +4 -3
- package/loader/loadPlugins.ts +7 -9
- package/main.ts +22 -36
- package/package.json +6 -5
- package/plugins/cipher.ts +1 -1
- package/plugins/config.ts +3 -4
- package/plugins/db.ts +4 -5
- package/plugins/jwt.ts +3 -2
- package/plugins/logger.ts +6 -6
- package/plugins/redis.ts +8 -12
- package/router/static.ts +3 -6
- package/sync/syncAll.ts +7 -12
- package/sync/syncApi.ts +4 -3
- package/sync/syncDb.ts +6 -5
- package/sync/syncDev.ts +9 -8
- package/sync/syncMenu.ts +174 -132
- package/tests/integration.test.ts +2 -6
- package/tests/redisHelper.test.ts +1 -2
- package/tests/validator.test.ts +611 -85
- package/types/befly.d.ts +7 -0
- package/types/cache.d.ts +73 -0
- package/types/common.d.ts +1 -37
- package/types/database.d.ts +5 -0
- package/types/index.ts +5 -5
- package/types/plugin.d.ts +1 -4
- package/types/redis.d.ts +37 -2
- package/types/table.d.ts +6 -44
- package/util.ts +283 -0
- package/tests/validator-advanced.test.ts +0 -653
- 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 -13
- package/types/tool.d.ts +0 -67
- package/types/validator.d.ts +0 -43
package/README.md
CHANGED
|
@@ -196,6 +196,89 @@ DB_NAME=/path/to/database.sqlite
|
|
|
196
196
|
DB_NAME=:memory:
|
|
197
197
|
```
|
|
198
198
|
|
|
199
|
+
## ⚙️ 项目配置文件
|
|
200
|
+
|
|
201
|
+
Befly 使用 `befly.config.ts` 作为统一配置文件:
|
|
202
|
+
|
|
203
|
+
```typescript
|
|
204
|
+
// befly.config.ts
|
|
205
|
+
export const beflyConfig = {
|
|
206
|
+
appName: '我的应用',
|
|
207
|
+
appPort: 3000,
|
|
208
|
+
appHost: '0.0.0.0',
|
|
209
|
+
|
|
210
|
+
// 数据库配置(优先使用环境变量)
|
|
211
|
+
db: {
|
|
212
|
+
type: 'mysql',
|
|
213
|
+
host: '127.0.0.1',
|
|
214
|
+
port: 3306,
|
|
215
|
+
username: 'root',
|
|
216
|
+
password: 'password',
|
|
217
|
+
database: 'my_database'
|
|
218
|
+
},
|
|
219
|
+
|
|
220
|
+
// Redis 配置
|
|
221
|
+
redis: {
|
|
222
|
+
host: '127.0.0.1',
|
|
223
|
+
port: 6379,
|
|
224
|
+
prefix: 'befly:'
|
|
225
|
+
},
|
|
226
|
+
|
|
227
|
+
// CORS 跨域配置
|
|
228
|
+
cors: {
|
|
229
|
+
origin: ['http://localhost:5173'],
|
|
230
|
+
methods: ['GET', 'POST', 'PUT', 'DELETE']
|
|
231
|
+
},
|
|
232
|
+
|
|
233
|
+
// Addon 插件配置
|
|
234
|
+
addons: {
|
|
235
|
+
admin: {
|
|
236
|
+
email: { host: 'smtp.qq.com' }
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
};
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
### 数据库连接
|
|
243
|
+
|
|
244
|
+
框架会自动从 `beflyConfig` 获取配置并建立连接,无需手动传参:
|
|
245
|
+
|
|
246
|
+
```typescript
|
|
247
|
+
import { Connect } from 'befly/lib/connect';
|
|
248
|
+
|
|
249
|
+
// 连接 SQL 数据库(配置自动从 beflyConfig.db 获取)
|
|
250
|
+
await Connect.connectSql();
|
|
251
|
+
|
|
252
|
+
// 连接 Redis(配置自动从 beflyConfig.redis 获取)
|
|
253
|
+
await Connect.connectRedis();
|
|
254
|
+
|
|
255
|
+
// 同时连接 SQL 和 Redis
|
|
256
|
+
await Connect.connect();
|
|
257
|
+
|
|
258
|
+
// 获取连接状态
|
|
259
|
+
const status = Connect.getStatus();
|
|
260
|
+
console.log(status.sql.connected); // true/false
|
|
261
|
+
console.log(status.redis.connected); // true/false
|
|
262
|
+
|
|
263
|
+
// 断开连接
|
|
264
|
+
await Connect.disconnect();
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### 配置文件迁移指南
|
|
268
|
+
|
|
269
|
+
如果你的项目之前使用 `app.config.ts`,请按以下步骤迁移:
|
|
270
|
+
|
|
271
|
+
1. **重命名文件**:`app.config.ts` → `befly.config.ts`
|
|
272
|
+
2. **更新导出名**:`config` → `beflyConfig`
|
|
273
|
+
|
|
274
|
+
```typescript
|
|
275
|
+
// 旧写法
|
|
276
|
+
export const config = { ... };
|
|
277
|
+
|
|
278
|
+
// 新写法
|
|
279
|
+
export const beflyConfig = { ... };
|
|
280
|
+
```
|
|
281
|
+
|
|
199
282
|
## 📖 文档
|
|
200
283
|
|
|
201
284
|
完整文档请访问 [`/docs` 目录](./docs/):
|
|
@@ -1,10 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Befly
|
|
3
|
-
*
|
|
2
|
+
* Befly 配置模块
|
|
3
|
+
* 自动加载 configs 目录下的配置文件并与默认配置合并
|
|
4
|
+
* 支持环境分离:befly.common.json + befly.dev/prod.json + befly.local.json
|
|
4
5
|
*/
|
|
6
|
+
import { scanConfig } from 'befly-shared/scanConfig';
|
|
7
|
+
|
|
5
8
|
import type { BeflyOptions } from './types/befly.js';
|
|
6
9
|
|
|
7
|
-
|
|
10
|
+
/** 默认配置 */
|
|
11
|
+
const defaultOptions: BeflyOptions = {
|
|
8
12
|
// ========== 核心参数 ==========
|
|
9
13
|
nodeEnv: (process.env.NODE_ENV as any) || 'development',
|
|
10
14
|
appName: '野蜂飞舞',
|
|
@@ -63,8 +67,24 @@ export const defaultOptions: Required<Omit<BeflyOptions, 'devPassword'>> = {
|
|
|
63
67
|
},
|
|
64
68
|
|
|
65
69
|
// ========== 禁用配置 ==========
|
|
66
|
-
/** 禁用的钩子列表 */
|
|
67
70
|
disableHooks: [],
|
|
68
|
-
|
|
69
|
-
|
|
71
|
+
disablePlugins: [],
|
|
72
|
+
hiddenMenus: [],
|
|
73
|
+
|
|
74
|
+
// ========== Addon 配置 ==========
|
|
75
|
+
addons: {}
|
|
70
76
|
};
|
|
77
|
+
|
|
78
|
+
// 确定环境
|
|
79
|
+
const nodeEnv = process.env.NODE_ENV || 'development';
|
|
80
|
+
const envSuffix = nodeEnv === 'production' ? 'prod' : 'dev';
|
|
81
|
+
|
|
82
|
+
// 使用 scanConfig 一次性加载并合并所有配置文件
|
|
83
|
+
// 合并顺序:defaultOptions ← befly.common.json ← befly.dev/prod.json ← befly.local.json
|
|
84
|
+
export const beflyConfig = (await scanConfig({
|
|
85
|
+
dirs: ['configs'],
|
|
86
|
+
files: ['befly.common', `befly.${envSuffix}`, 'befly.local'],
|
|
87
|
+
extensions: ['.json'],
|
|
88
|
+
mode: 'merge',
|
|
89
|
+
defaults: defaultOptions
|
|
90
|
+
})) as BeflyOptions;
|
package/checks/checkApp.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// 内部依赖
|
|
2
2
|
import { join } from 'node:path';
|
|
3
|
-
import { existsSync, mkdirSync } from 'node:fs';
|
|
3
|
+
import { existsSync, mkdirSync, writeFileSync } from 'node:fs';
|
|
4
4
|
|
|
5
5
|
// 相对导入
|
|
6
6
|
import { Logger } from '../lib/logger.js';
|
|
@@ -24,6 +24,36 @@ export async function checkApp(): Promise<void> {
|
|
|
24
24
|
if (!existsSync(logsDir)) {
|
|
25
25
|
mkdirSync(logsDir, { recursive: true });
|
|
26
26
|
}
|
|
27
|
+
|
|
28
|
+
// 检查并创建 configs 目录和配置文件
|
|
29
|
+
const configsDir = join(projectDir, 'configs');
|
|
30
|
+
if (!existsSync(configsDir)) {
|
|
31
|
+
mkdirSync(configsDir, { recursive: true });
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// 检查并创建 befly.common.json
|
|
35
|
+
const beflyJsonPath = join(configsDir, 'befly.common.json');
|
|
36
|
+
if (!existsSync(beflyJsonPath)) {
|
|
37
|
+
writeFileSync(beflyJsonPath, '{}', 'utf-8');
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// 检查并创建 befly.dev.json
|
|
41
|
+
const beflyDevJsonPath = join(configsDir, 'befly.dev.json');
|
|
42
|
+
if (!existsSync(beflyDevJsonPath)) {
|
|
43
|
+
writeFileSync(beflyDevJsonPath, '{}', 'utf-8');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// 检查并创建 befly.prod.json
|
|
47
|
+
const beflyProdJsonPath = join(configsDir, 'befly.prod.json');
|
|
48
|
+
if (!existsSync(beflyProdJsonPath)) {
|
|
49
|
+
writeFileSync(beflyProdJsonPath, '{}', 'utf-8');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
// 检查并创建 befly.local.json
|
|
53
|
+
const beflyLocalJsonPath = join(configsDir, 'befly.local.json');
|
|
54
|
+
if (!existsSync(beflyLocalJsonPath)) {
|
|
55
|
+
writeFileSync(beflyLocalJsonPath, '{}', 'utf-8');
|
|
56
|
+
}
|
|
27
57
|
} catch (error: any) {
|
|
28
58
|
Logger.error('项目结构检查过程中出错', error);
|
|
29
59
|
throw error;
|
package/hooks/cors.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// 相对导入
|
|
2
2
|
import { setCorsOptions } from '../util.js';
|
|
3
|
+
import { beflyConfig } from '../befly.config.js';
|
|
3
4
|
|
|
4
5
|
// 类型导入
|
|
5
6
|
import type { Hook } from '../types/hook.js';
|
|
@@ -24,11 +25,10 @@ const hook: Hook = {
|
|
|
24
25
|
credentials: 'true'
|
|
25
26
|
};
|
|
26
27
|
|
|
27
|
-
const
|
|
28
|
-
const config = { ...defaultConfig, ...userConfig };
|
|
28
|
+
const corsConfig = { ...defaultConfig, ...(beflyConfig.cors || {}) };
|
|
29
29
|
|
|
30
30
|
// 设置 CORS 响应头
|
|
31
|
-
const headers = setCorsOptions(req,
|
|
31
|
+
const headers = setCorsOptions(req, corsConfig);
|
|
32
32
|
|
|
33
33
|
ctx.corsHeaders = headers;
|
|
34
34
|
|
package/hooks/parser.ts
CHANGED
|
@@ -53,12 +53,12 @@ const hook: Hook = {
|
|
|
53
53
|
}
|
|
54
54
|
} else {
|
|
55
55
|
// 不支持的 Content-Type
|
|
56
|
-
ctx.response = ErrorResponse(ctx, '无效的请求参数格式');
|
|
56
|
+
ctx.response = ErrorResponse(ctx, '无效的请求参数格式', 1, null, { location: 'content-type', value: contentType });
|
|
57
57
|
return;
|
|
58
58
|
}
|
|
59
|
-
} catch (e) {
|
|
59
|
+
} catch (e: any) {
|
|
60
60
|
// 解析失败
|
|
61
|
-
ctx.response = ErrorResponse(ctx, '无效的请求参数格式');
|
|
61
|
+
ctx.response = ErrorResponse(ctx, '无效的请求参数格式', 1, null, { location: 'body', error: e.message });
|
|
62
62
|
return;
|
|
63
63
|
}
|
|
64
64
|
}
|
package/hooks/validator.ts
CHANGED
|
@@ -23,7 +23,7 @@ const hook: Hook = {
|
|
|
23
23
|
const result = Validator.validate(ctx.body, ctx.api.fields, ctx.api.required || []);
|
|
24
24
|
|
|
25
25
|
if (result.code !== 0) {
|
|
26
|
-
ctx.response = ErrorResponse(ctx, '
|
|
26
|
+
ctx.response = ErrorResponse(ctx, result.firstError || '参数验证失败', 1, null, result.fieldErrors);
|
|
27
27
|
return;
|
|
28
28
|
}
|
|
29
29
|
}
|
package/lib/cacheHelper.ts
CHANGED
|
@@ -46,8 +46,6 @@ export class CacheHelper {
|
|
|
46
46
|
|
|
47
47
|
if (result === null) {
|
|
48
48
|
Logger.warn('⚠️ 接口缓存失败');
|
|
49
|
-
} else {
|
|
50
|
-
Logger.info(`✅ 已缓存 ${apiList.length} 个接口到 Redis (Key: ${RedisKeys.apisAll()})`);
|
|
51
49
|
}
|
|
52
50
|
} catch (error: any) {
|
|
53
51
|
Logger.error({ err: error }, '⚠️ 接口缓存异常');
|
|
@@ -78,8 +76,6 @@ export class CacheHelper {
|
|
|
78
76
|
|
|
79
77
|
if (result === null) {
|
|
80
78
|
Logger.warn('⚠️ 菜单缓存失败');
|
|
81
|
-
} else {
|
|
82
|
-
Logger.info(`✅ 已缓存 ${menus.length} 个菜单到 Redis (Key: ${RedisKeys.menusAll()})`);
|
|
83
79
|
}
|
|
84
80
|
} catch (error: any) {
|
|
85
81
|
Logger.warn({ err: error }, '⚠️ 菜单缓存异常');
|
|
@@ -159,8 +155,6 @@ export class CacheHelper {
|
|
|
159
155
|
|
|
160
156
|
// 统计成功缓存的角色数
|
|
161
157
|
const cachedRoles = results.filter((r) => r > 0).length;
|
|
162
|
-
|
|
163
|
-
Logger.info(`✅ 已缓存 ${cachedRoles} 个角色的接口权限(共 ${cacheOperations.reduce((sum, op) => sum + op.apiPaths.length, 0)} 个接口)`);
|
|
164
158
|
} catch (error: any) {
|
|
165
159
|
Logger.warn({ err: error }, '⚠️ 角色权限缓存异常');
|
|
166
160
|
}
|
package/lib/cipher.ts
CHANGED
package/lib/connect.ts
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* 数据库连接管理器
|
|
3
3
|
* 统一管理 SQL 和 Redis 连接
|
|
4
|
+
* 配置从 beflyConfig 全局对象获取
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
7
|
import { SQL, RedisClient } from 'bun';
|
|
7
8
|
|
|
9
|
+
import { beflyConfig } from '../befly.config.js';
|
|
8
10
|
import { Logger } from './logger.js';
|
|
9
11
|
|
|
10
|
-
import type { BeflyOptions, DatabaseConfig, RedisConfig } from '../types/befly.js';
|
|
11
|
-
import type { SqlClientOptions } from '../types/database.js';
|
|
12
|
-
|
|
13
12
|
/**
|
|
14
13
|
* 数据库连接管理器
|
|
15
14
|
* 使用静态方法管理全局单例连接
|
|
15
|
+
* 所有配置从 beflyConfig 自动获取
|
|
16
16
|
*/
|
|
17
17
|
export class Connect {
|
|
18
18
|
private static sqlClient: SQL | null = null;
|
|
@@ -29,10 +29,12 @@ export class Connect {
|
|
|
29
29
|
|
|
30
30
|
/**
|
|
31
31
|
* 连接 SQL 数据库
|
|
32
|
-
*
|
|
32
|
+
* 配置从 beflyConfig.db 获取
|
|
33
33
|
* @returns SQL 客户端实例
|
|
34
34
|
*/
|
|
35
|
-
static async connectSql(
|
|
35
|
+
static async connectSql(): Promise<SQL> {
|
|
36
|
+
const config = beflyConfig.db || {};
|
|
37
|
+
|
|
36
38
|
// 构建数据库连接字符串
|
|
37
39
|
const type = config.type || 'mysql';
|
|
38
40
|
const host = config.host || '127.0.0.1';
|
|
@@ -135,10 +137,12 @@ export class Connect {
|
|
|
135
137
|
|
|
136
138
|
/**
|
|
137
139
|
* 连接 Redis
|
|
138
|
-
*
|
|
140
|
+
* 配置从 beflyConfig.redis 获取
|
|
139
141
|
* @returns Redis 客户端实例
|
|
140
142
|
*/
|
|
141
|
-
static async connectRedis(
|
|
143
|
+
static async connectRedis(): Promise<RedisClient> {
|
|
144
|
+
const config = beflyConfig.redis || {};
|
|
145
|
+
|
|
142
146
|
try {
|
|
143
147
|
// 构建 Redis URL
|
|
144
148
|
const host = config.host || '127.0.0.1';
|
|
@@ -208,21 +212,15 @@ export class Connect {
|
|
|
208
212
|
|
|
209
213
|
/**
|
|
210
214
|
* 连接所有数据库(SQL + Redis)
|
|
211
|
-
*
|
|
212
|
-
* @param options - 连接选项
|
|
215
|
+
* 配置从 beflyConfig 自动获取
|
|
213
216
|
*/
|
|
214
|
-
static async connect(
|
|
217
|
+
static async connect(): Promise<void> {
|
|
215
218
|
try {
|
|
216
|
-
//
|
|
217
|
-
|
|
218
|
-
const sqlConfig = options?.db || {};
|
|
219
|
-
if (sqlConfig) {
|
|
220
|
-
await this.connectSql(sqlConfig);
|
|
221
|
-
}
|
|
219
|
+
// 连接 SQL
|
|
220
|
+
await this.connectSql();
|
|
222
221
|
|
|
223
|
-
//
|
|
224
|
-
|
|
225
|
-
await this.connectRedis(redisConfig);
|
|
222
|
+
// 连接 Redis
|
|
223
|
+
await this.connectRedis();
|
|
226
224
|
} catch (error: any) {
|
|
227
225
|
Logger.error({ err: error }, '数据库初始化失败');
|
|
228
226
|
await this.disconnect();
|
package/lib/jwt.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import { createSigner, createVerifier, createDecoder } from 'fast-jwt';
|
|
6
6
|
|
|
7
7
|
import type { AuthConfig } from '../types/befly.js';
|
|
8
|
-
import type { JwtPayload, JwtSignOptions, JwtVerifyOptions, JwtDecoded, JwtHeader } from '
|
|
8
|
+
import type { JwtPayload, JwtSignOptions, JwtVerifyOptions, JwtDecoded, JwtHeader } from 'befly-shared/types';
|
|
9
9
|
|
|
10
10
|
interface FastJwtComplete {
|
|
11
11
|
header: JwtHeader;
|
package/lib/logger.ts
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
import pino from 'pino';
|
|
6
6
|
import { join } from 'pathe';
|
|
7
7
|
|
|
8
|
-
import type { LoggerConfig } from '
|
|
8
|
+
import type { LoggerConfig } from 'befly-shared/types';
|
|
9
9
|
|
|
10
10
|
let instance: pino.Logger | null = null;
|
|
11
11
|
let mockInstance: pino.Logger | null = null;
|