befly 3.8.29 → 3.8.31
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 +91 -6
- package/checks/checkApi.ts +2 -1
- package/checks/checkApp.ts +31 -1
- package/checks/checkTable.ts +3 -2
- package/hooks/cors.ts +3 -3
- package/hooks/parser.ts +8 -6
- package/hooks/permission.ts +12 -5
- package/hooks/validator.ts +1 -1
- package/lib/cacheHelper.ts +73 -65
- package/lib/cipher.ts +2 -1
- package/lib/connect.ts +23 -52
- package/lib/dbHelper.ts +14 -11
- package/lib/jwt.ts +58 -437
- package/lib/logger.ts +76 -197
- package/lib/redisHelper.ts +163 -1
- package/lib/sqlBuilder.ts +2 -1
- package/lib/validator.ts +150 -384
- package/loader/loadApis.ts +4 -7
- package/loader/loadHooks.ts +6 -5
- package/loader/loadPlugins.ts +11 -13
- package/main.ts +26 -53
- package/package.json +10 -8
- package/paths.ts +0 -6
- package/plugins/cipher.ts +1 -1
- package/plugins/config.ts +3 -4
- package/plugins/db.ts +6 -7
- package/plugins/jwt.ts +7 -6
- package/plugins/logger.ts +6 -6
- package/plugins/redis.ts +9 -13
- package/router/api.ts +2 -2
- package/router/static.ts +4 -8
- package/sync/syncAll.ts +8 -13
- package/sync/syncApi.ts +14 -10
- package/sync/syncDb/apply.ts +1 -2
- package/sync/syncDb.ts +12 -15
- package/sync/syncDev.ts +19 -56
- package/sync/syncMenu.ts +182 -137
- package/tests/cacheHelper.test.ts +327 -0
- package/tests/dbHelper-columns.test.ts +5 -20
- package/tests/dbHelper-execute.test.ts +14 -68
- package/tests/fields-redis-cache.test.ts +5 -3
- package/tests/integration.test.ts +17 -32
- package/tests/jwt.test.ts +36 -94
- package/tests/logger.test.ts +32 -34
- package/tests/redisHelper.test.ts +271 -2
- package/tests/redisKeys.test.ts +76 -0
- package/tests/sync-connection.test.ts +0 -6
- package/tests/syncDb-constants.test.ts +12 -12
- package/tests/util.test.ts +5 -1
- package/tests/validator.test.ts +611 -85
- package/types/befly.d.ts +9 -15
- package/types/cache.d.ts +73 -0
- package/types/common.d.ts +10 -128
- package/types/database.d.ts +221 -5
- package/types/index.ts +6 -5
- package/types/plugin.d.ts +1 -4
- package/types/redis.d.ts +37 -2
- package/types/table.d.ts +175 -0
- package/config.ts +0 -70
- package/hooks/_rateLimit.ts +0 -64
- package/lib/regexAliases.ts +0 -59
- package/lib/xml.ts +0 -383
- package/tests/validator-advanced.test.ts +0 -653
- package/tests/xml.test.ts +0 -101
- 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 -43
- package/types/tool.d.ts +0 -67
- package/types/validator.d.ts +0 -43
package/README.md
CHANGED
|
@@ -37,12 +37,14 @@ bunx befly init
|
|
|
37
37
|
|
|
38
38
|
```typescript
|
|
39
39
|
// main.ts
|
|
40
|
-
import {
|
|
40
|
+
import { Befly } from 'befly';
|
|
41
41
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
42
|
+
const app = new Befly({
|
|
43
|
+
appName: 'My API',
|
|
44
|
+
appPort: 3000
|
|
45
45
|
});
|
|
46
|
+
|
|
47
|
+
await app.start();
|
|
46
48
|
```
|
|
47
49
|
|
|
48
50
|
运行项目:
|
|
@@ -55,7 +57,7 @@ bun run main.ts
|
|
|
55
57
|
|
|
56
58
|
```typescript
|
|
57
59
|
// apis/user/hello.ts
|
|
58
|
-
import type { ApiRoute } from 'befly';
|
|
60
|
+
import type { ApiRoute } from 'befly/types/index';
|
|
59
61
|
|
|
60
62
|
export default {
|
|
61
63
|
name: '问候接口',
|
|
@@ -79,7 +81,7 @@ export default {
|
|
|
79
81
|
### TypeScript 全面支持
|
|
80
82
|
|
|
81
83
|
```typescript
|
|
82
|
-
import type { ApiRoute, BeflyContext } from 'befly';
|
|
84
|
+
import type { ApiRoute, BeflyContext } from 'befly/types/index';
|
|
83
85
|
import type { User } from './types/models';
|
|
84
86
|
|
|
85
87
|
export default {
|
|
@@ -194,6 +196,89 @@ DB_NAME=/path/to/database.sqlite
|
|
|
194
196
|
DB_NAME=:memory:
|
|
195
197
|
```
|
|
196
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
|
+
|
|
197
282
|
## 📖 文档
|
|
198
283
|
|
|
199
284
|
完整文档请访问 [`/docs` 目录](./docs/):
|
package/checks/checkApi.ts
CHANGED
|
@@ -3,7 +3,8 @@ import { existsSync } from 'node:fs';
|
|
|
3
3
|
|
|
4
4
|
// 外部依赖
|
|
5
5
|
import { isPlainObject } from 'es-toolkit/compat';
|
|
6
|
-
import {
|
|
6
|
+
import { scanFiles } from 'befly-shared/scanFiles';
|
|
7
|
+
import { scanAddons, getAddonDir, addonDirExists } from 'befly-shared/addonHelper';
|
|
7
8
|
|
|
8
9
|
// 相对导入
|
|
9
10
|
import { Logger } from '../lib/logger.js';
|
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/checks/checkTable.ts
CHANGED
|
@@ -3,14 +3,15 @@ import { existsSync } from 'node:fs';
|
|
|
3
3
|
|
|
4
4
|
// 外部依赖
|
|
5
5
|
import { basename } from 'pathe';
|
|
6
|
-
import {
|
|
6
|
+
import { scanFiles } from 'befly-shared/scanFiles';
|
|
7
|
+
import { scanAddons, getAddonDir } from 'befly-shared/addonHelper';
|
|
7
8
|
|
|
8
9
|
// 相对导入
|
|
9
10
|
import { Logger } from '../lib/logger.js';
|
|
10
11
|
import { projectTableDir } from '../paths.js';
|
|
11
12
|
|
|
12
13
|
// 类型导入
|
|
13
|
-
import type { FieldDefinition } from '
|
|
14
|
+
import type { FieldDefinition } from 'befly-shared/types';
|
|
14
15
|
|
|
15
16
|
/**
|
|
16
17
|
* 表文件信息接口
|
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
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
// 外部依赖
|
|
2
2
|
import { isPlainObject, isEmpty } from 'es-toolkit/compat';
|
|
3
|
-
import { pickFields } from 'befly-
|
|
3
|
+
import { pickFields } from 'befly-shared/pickFields';
|
|
4
|
+
import { XMLParser } from 'fast-xml-parser';
|
|
4
5
|
|
|
5
6
|
// 相对导入
|
|
6
|
-
import { Xml } from '../lib/xml.js';
|
|
7
7
|
import { ErrorResponse } from '../util.js';
|
|
8
8
|
|
|
9
9
|
// 类型导入
|
|
10
10
|
import type { Hook } from '../types/hook.js';
|
|
11
11
|
|
|
12
|
+
const xmlParser = new XMLParser();
|
|
13
|
+
|
|
12
14
|
/**
|
|
13
15
|
* 请求参数解析钩子
|
|
14
16
|
* - GET 请求:解析 URL 查询参数
|
|
@@ -43,7 +45,7 @@ const hook: Hook = {
|
|
|
43
45
|
} else if (contentType.includes('application/xml') || contentType.includes('text/xml')) {
|
|
44
46
|
// XML 格式
|
|
45
47
|
const text = await ctx.req.text();
|
|
46
|
-
const body =
|
|
48
|
+
const body = xmlParser.parse(text);
|
|
47
49
|
if (isPlainObject(ctx.api.fields) && !isEmpty(ctx.api.fields)) {
|
|
48
50
|
ctx.body = pickFields(body, Object.keys(ctx.api.fields));
|
|
49
51
|
} else {
|
|
@@ -51,12 +53,12 @@ const hook: Hook = {
|
|
|
51
53
|
}
|
|
52
54
|
} else {
|
|
53
55
|
// 不支持的 Content-Type
|
|
54
|
-
ctx.response = ErrorResponse(ctx, '无效的请求参数格式');
|
|
56
|
+
ctx.response = ErrorResponse(ctx, '无效的请求参数格式', 1, null, { location: 'content-type', value: contentType });
|
|
55
57
|
return;
|
|
56
58
|
}
|
|
57
|
-
} catch (e) {
|
|
59
|
+
} catch (e: any) {
|
|
58
60
|
// 解析失败
|
|
59
|
-
ctx.response = ErrorResponse(ctx, '无效的请求参数格式');
|
|
61
|
+
ctx.response = ErrorResponse(ctx, '无效的请求参数格式', 1, null, { location: 'body', error: e.message });
|
|
60
62
|
return;
|
|
61
63
|
}
|
|
62
64
|
}
|
package/hooks/permission.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
// 相对导入
|
|
2
2
|
import { ErrorResponse } from '../util.js';
|
|
3
|
+
import { RedisKeys } from 'befly-shared/redisKeys';
|
|
3
4
|
|
|
4
5
|
// 类型导入
|
|
5
6
|
import type { Hook } from '../types/hook.js';
|
|
@@ -35,11 +36,17 @@ const hook: Hook = {
|
|
|
35
36
|
// 4. 角色权限检查
|
|
36
37
|
let hasPermission = false;
|
|
37
38
|
if (ctx.user.roleCode && befly.redis) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
39
|
+
try {
|
|
40
|
+
// 验证角色权限
|
|
41
|
+
const apiPath = `${ctx.req.method}${new URL(ctx.req.url).pathname}`;
|
|
42
|
+
const roleApisKey = RedisKeys.roleApis(ctx.user.roleCode);
|
|
43
|
+
const isMember = await befly.redis.sismember(roleApisKey, apiPath);
|
|
44
|
+
hasPermission = isMember === 1;
|
|
45
|
+
} catch (error) {
|
|
46
|
+
// Redis 异常时降级为拒绝访问
|
|
47
|
+
befly.logger.warn({ err: error, route: ctx.route }, 'Redis 权限检查失败');
|
|
48
|
+
hasPermission = false;
|
|
49
|
+
}
|
|
43
50
|
}
|
|
44
51
|
|
|
45
52
|
if (!hasPermission) {
|
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
|
@@ -4,6 +4,8 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import { Logger } from './logger.js';
|
|
7
|
+
import { RedisKeys } from 'befly-shared/redisKeys';
|
|
8
|
+
|
|
7
9
|
import type { BeflyContext } from '../types/befly.js';
|
|
8
10
|
|
|
9
11
|
/**
|
|
@@ -26,29 +28,27 @@ export class CacheHelper {
|
|
|
26
28
|
async cacheApis(): Promise<void> {
|
|
27
29
|
try {
|
|
28
30
|
// 检查表是否存在
|
|
29
|
-
const tableExists = await this.befly.db.tableExists('
|
|
31
|
+
const tableExists = await this.befly.db.tableExists('addon_admin_api');
|
|
30
32
|
if (!tableExists) {
|
|
31
33
|
Logger.warn('⚠️ 接口表不存在,跳过接口缓存');
|
|
32
34
|
return;
|
|
33
35
|
}
|
|
34
36
|
|
|
35
|
-
//
|
|
37
|
+
// 从数据库查询所有接口
|
|
36
38
|
const apiList = await this.befly.db.getAll({
|
|
37
|
-
table: '
|
|
39
|
+
table: 'addon_admin_api',
|
|
38
40
|
fields: ['id', 'name', 'path', 'method', 'description', 'addonName', 'addonTitle'],
|
|
39
41
|
orderBy: ['addonName#ASC', 'path#ASC']
|
|
40
42
|
});
|
|
41
43
|
|
|
42
44
|
// 缓存到 Redis
|
|
43
|
-
const result = await this.befly.redis.setObject(
|
|
45
|
+
const result = await this.befly.redis.setObject(RedisKeys.apisAll(), apiList);
|
|
44
46
|
|
|
45
47
|
if (result === null) {
|
|
46
48
|
Logger.warn('⚠️ 接口缓存失败');
|
|
47
|
-
} else {
|
|
48
|
-
Logger.info(`✅ 已缓存 ${apiList.length} 个接口到 Redis (Key: apis:all)`);
|
|
49
49
|
}
|
|
50
50
|
} catch (error: any) {
|
|
51
|
-
Logger.error('⚠️
|
|
51
|
+
Logger.error({ err: error }, '⚠️ 接口缓存异常');
|
|
52
52
|
}
|
|
53
53
|
}
|
|
54
54
|
|
|
@@ -58,7 +58,7 @@ export class CacheHelper {
|
|
|
58
58
|
async cacheMenus(): Promise<void> {
|
|
59
59
|
try {
|
|
60
60
|
// 检查表是否存在
|
|
61
|
-
const tableExists = await this.befly.db.tableExists('
|
|
61
|
+
const tableExists = await this.befly.db.tableExists('addon_admin_menu');
|
|
62
62
|
if (!tableExists) {
|
|
63
63
|
Logger.warn('⚠️ 菜单表不存在,跳过菜单缓存');
|
|
64
64
|
return;
|
|
@@ -66,86 +66,97 @@ export class CacheHelper {
|
|
|
66
66
|
|
|
67
67
|
// 从数据库查询所有菜单
|
|
68
68
|
const menus = await this.befly.db.getAll({
|
|
69
|
-
table: '
|
|
69
|
+
table: 'addon_admin_menu',
|
|
70
70
|
fields: ['id', 'pid', 'name', 'path', 'icon', 'type', 'sort'],
|
|
71
71
|
orderBy: ['sort#ASC', 'id#ASC']
|
|
72
72
|
});
|
|
73
73
|
|
|
74
74
|
// 缓存到 Redis
|
|
75
|
-
const result = await this.befly.redis.setObject(
|
|
75
|
+
const result = await this.befly.redis.setObject(RedisKeys.menusAll(), menus);
|
|
76
76
|
|
|
77
77
|
if (result === null) {
|
|
78
78
|
Logger.warn('⚠️ 菜单缓存失败');
|
|
79
|
-
} else {
|
|
80
|
-
Logger.info(`✅ 已缓存 ${menus.length} 个菜单到 Redis (Key: menus:all)`);
|
|
81
79
|
}
|
|
82
80
|
} catch (error: any) {
|
|
83
|
-
|
|
84
|
-
Logger.warn('⚠️ 菜单缓存异常:', errorMessage);
|
|
81
|
+
Logger.warn({ err: error }, '⚠️ 菜单缓存异常');
|
|
85
82
|
}
|
|
86
83
|
}
|
|
87
84
|
|
|
88
85
|
/**
|
|
89
86
|
* 缓存所有角色的接口权限到 Redis
|
|
87
|
+
* 优化:使用 Promise.all 利用 Bun Redis 自动 pipeline 特性
|
|
90
88
|
*/
|
|
91
89
|
async cacheRolePermissions(): Promise<void> {
|
|
92
90
|
try {
|
|
93
91
|
// 检查表是否存在
|
|
94
|
-
const apiTableExists = await this.befly.db.tableExists('
|
|
95
|
-
const roleTableExists = await this.befly.db.tableExists('
|
|
92
|
+
const apiTableExists = await this.befly.db.tableExists('addon_admin_api');
|
|
93
|
+
const roleTableExists = await this.befly.db.tableExists('addon_admin_role');
|
|
96
94
|
|
|
97
95
|
if (!apiTableExists || !roleTableExists) {
|
|
98
96
|
Logger.warn('⚠️ 接口或角色表不存在,跳过角色权限缓存');
|
|
99
97
|
return;
|
|
100
98
|
}
|
|
101
99
|
|
|
102
|
-
//
|
|
103
|
-
const roles = await
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
100
|
+
// 并行查询角色和接口(利用自动 pipeline)
|
|
101
|
+
const [roles, allApis] = await Promise.all([
|
|
102
|
+
this.befly.db.getAll({
|
|
103
|
+
table: 'addon_admin_role',
|
|
104
|
+
fields: ['id', 'code', 'apis']
|
|
105
|
+
}),
|
|
106
|
+
this.befly.db.getAll({
|
|
107
|
+
table: 'addon_admin_api',
|
|
108
|
+
fields: ['id', 'path', 'method']
|
|
109
|
+
})
|
|
110
|
+
]);
|
|
111
|
+
|
|
112
|
+
// 构建接口 ID -> 路径的映射(避免重复过滤)
|
|
113
|
+
const apiMap = new Map<number, string>();
|
|
114
|
+
for (const api of allApis) {
|
|
115
|
+
apiMap.set(api.id, `${api.method}${api.path}`);
|
|
116
|
+
}
|
|
107
117
|
|
|
108
|
-
//
|
|
109
|
-
const
|
|
110
|
-
table: 'core_api',
|
|
111
|
-
fields: ['id', 'name', 'path', 'method', 'description', 'addonName']
|
|
112
|
-
});
|
|
118
|
+
// 收集需要缓存的角色权限
|
|
119
|
+
const cacheOperations: Array<{ roleCode: string; apiPaths: string[] }> = [];
|
|
113
120
|
|
|
114
|
-
// 为每个角色缓存接口权限
|
|
115
|
-
let cachedRoles = 0;
|
|
116
121
|
for (const role of roles) {
|
|
117
122
|
if (!role.apis) continue;
|
|
118
123
|
|
|
119
|
-
// 解析角色的接口 ID
|
|
120
|
-
const
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
124
|
+
// 解析角色的接口 ID 列表并映射到路径
|
|
125
|
+
const apiPaths: string[] = [];
|
|
126
|
+
const apiIds = role.apis.split(',');
|
|
127
|
+
|
|
128
|
+
for (const idStr of apiIds) {
|
|
129
|
+
const id = parseInt(idStr.trim());
|
|
130
|
+
if (!isNaN(id)) {
|
|
131
|
+
const path = apiMap.get(id);
|
|
132
|
+
if (path) {
|
|
133
|
+
apiPaths.push(path);
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
129
137
|
|
|
130
|
-
|
|
131
|
-
|
|
138
|
+
if (apiPaths.length > 0) {
|
|
139
|
+
cacheOperations.push({ roleCode: role.code, apiPaths: apiPaths });
|
|
140
|
+
}
|
|
141
|
+
}
|
|
132
142
|
|
|
133
|
-
|
|
134
|
-
|
|
143
|
+
if (cacheOperations.length === 0) {
|
|
144
|
+
Logger.info('✅ 没有需要缓存的角色权限');
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
135
147
|
|
|
136
|
-
|
|
137
|
-
|
|
148
|
+
// 批量删除旧缓存(利用自动 pipeline)
|
|
149
|
+
const deletePromises = cacheOperations.map((op) => this.befly.redis.del(RedisKeys.roleApis(op.roleCode)));
|
|
150
|
+
await Promise.all(deletePromises);
|
|
138
151
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
}
|
|
143
|
-
}
|
|
152
|
+
// 批量添加新缓存(利用自动 pipeline)
|
|
153
|
+
const addPromises = cacheOperations.map((op) => this.befly.redis.sadd(RedisKeys.roleApis(op.roleCode), op.apiPaths));
|
|
154
|
+
const results = await Promise.all(addPromises);
|
|
144
155
|
|
|
145
|
-
|
|
156
|
+
// 统计成功缓存的角色数
|
|
157
|
+
const cachedRoles = results.filter((r) => r > 0).length;
|
|
146
158
|
} catch (error: any) {
|
|
147
|
-
|
|
148
|
-
Logger.warn('⚠️ 角色权限缓存异常:', errorMessage);
|
|
159
|
+
Logger.warn({ err: error }, '⚠️ 角色权限缓存异常');
|
|
149
160
|
}
|
|
150
161
|
}
|
|
151
162
|
|
|
@@ -169,10 +180,10 @@ export class CacheHelper {
|
|
|
169
180
|
*/
|
|
170
181
|
async getApis(): Promise<any[]> {
|
|
171
182
|
try {
|
|
172
|
-
const apis = await this.befly.redis.getObject<any[]>(
|
|
183
|
+
const apis = await this.befly.redis.getObject<any[]>(RedisKeys.apisAll());
|
|
173
184
|
return apis || [];
|
|
174
185
|
} catch (error: any) {
|
|
175
|
-
Logger.error('
|
|
186
|
+
Logger.error({ err: error }, '获取接口缓存失败');
|
|
176
187
|
return [];
|
|
177
188
|
}
|
|
178
189
|
}
|
|
@@ -183,10 +194,10 @@ export class CacheHelper {
|
|
|
183
194
|
*/
|
|
184
195
|
async getMenus(): Promise<any[]> {
|
|
185
196
|
try {
|
|
186
|
-
const menus = await this.befly.redis.getObject<any[]>(
|
|
197
|
+
const menus = await this.befly.redis.getObject<any[]>(RedisKeys.menusAll());
|
|
187
198
|
return menus || [];
|
|
188
199
|
} catch (error: any) {
|
|
189
|
-
Logger.error('
|
|
200
|
+
Logger.error({ err: error }, '获取菜单缓存失败');
|
|
190
201
|
return [];
|
|
191
202
|
}
|
|
192
203
|
}
|
|
@@ -198,11 +209,10 @@ export class CacheHelper {
|
|
|
198
209
|
*/
|
|
199
210
|
async getRolePermissions(roleCode: string): Promise<string[]> {
|
|
200
211
|
try {
|
|
201
|
-
const
|
|
202
|
-
const permissions = await this.befly.redis.smembers(redisKey);
|
|
212
|
+
const permissions = await this.befly.redis.smembers(RedisKeys.roleApis(roleCode));
|
|
203
213
|
return permissions || [];
|
|
204
214
|
} catch (error: any) {
|
|
205
|
-
Logger.error(
|
|
215
|
+
Logger.error({ err: error, roleCode: roleCode }, '获取角色权限缓存失败');
|
|
206
216
|
return [];
|
|
207
217
|
}
|
|
208
218
|
}
|
|
@@ -215,11 +225,10 @@ export class CacheHelper {
|
|
|
215
225
|
*/
|
|
216
226
|
async checkRolePermission(roleCode: string, apiPath: string): Promise<boolean> {
|
|
217
227
|
try {
|
|
218
|
-
const
|
|
219
|
-
const result = await this.befly.redis.sismember(redisKey, apiPath);
|
|
228
|
+
const result = await this.befly.redis.sismember(RedisKeys.roleApis(roleCode), apiPath);
|
|
220
229
|
return result === 1;
|
|
221
230
|
} catch (error: any) {
|
|
222
|
-
Logger.error(
|
|
231
|
+
Logger.error({ err: error, roleCode: roleCode }, '检查角色权限失败');
|
|
223
232
|
return false;
|
|
224
233
|
}
|
|
225
234
|
}
|
|
@@ -231,15 +240,14 @@ export class CacheHelper {
|
|
|
231
240
|
*/
|
|
232
241
|
async deleteRolePermissions(roleCode: string): Promise<boolean> {
|
|
233
242
|
try {
|
|
234
|
-
const
|
|
235
|
-
const result = await this.befly.redis.del(redisKey);
|
|
243
|
+
const result = await this.befly.redis.del(RedisKeys.roleApis(roleCode));
|
|
236
244
|
if (result > 0) {
|
|
237
245
|
Logger.info(`✅ 已删除角色 ${roleCode} 的权限缓存`);
|
|
238
246
|
return true;
|
|
239
247
|
}
|
|
240
248
|
return false;
|
|
241
249
|
} catch (error: any) {
|
|
242
|
-
Logger.error(
|
|
250
|
+
Logger.error({ err: error, roleCode: roleCode }, '删除角色权限缓存失败');
|
|
243
251
|
return false;
|
|
244
252
|
}
|
|
245
253
|
}
|
package/lib/cipher.ts
CHANGED