befly 3.8.30 → 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 +83 -0
- 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 +4 -3
- 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/config.ts +0 -70
- 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/sync/syncMenu.ts
CHANGED
|
@@ -1,82 +1,129 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* SyncMenu 命令 - 同步菜单数据到数据库
|
|
3
|
-
*
|
|
3
|
+
* 说明:扫描 addon 的 views 目录和项目的 menus.json,同步菜单数据
|
|
4
4
|
*
|
|
5
5
|
* 流程:
|
|
6
|
-
* 1. 扫描所有 addon 的
|
|
7
|
-
* 2.
|
|
8
|
-
* 3.
|
|
9
|
-
* 4.
|
|
10
|
-
* 5.
|
|
11
|
-
* 6.
|
|
12
|
-
* 7.
|
|
13
|
-
* 8. 强制删除配置中不存在的菜单记录
|
|
6
|
+
* 1. 扫描所有 addon 的 views 目录下的 meta.json 文件
|
|
7
|
+
* 2. 根据目录层级构建菜单树(无层级限制)
|
|
8
|
+
* 3. 读取项目的 menus.json 文件(手动配置的菜单)
|
|
9
|
+
* 4. 根据菜单的 path 字段检查是否存在
|
|
10
|
+
* 5. 存在则更新其他字段(name、sort、pid)
|
|
11
|
+
* 6. 不存在则新增菜单记录
|
|
12
|
+
* 7. 强制删除配置中不存在的菜单记录
|
|
14
13
|
* 注:state 字段由框架自动管理(1=正常,2=禁用,0=删除)
|
|
15
14
|
*/
|
|
16
15
|
|
|
16
|
+
import { existsSync } from 'node:fs';
|
|
17
|
+
import { readdir, readFile } from 'node:fs/promises';
|
|
17
18
|
import { join } from 'pathe';
|
|
18
|
-
|
|
19
|
+
|
|
19
20
|
import { Connect } from '../lib/connect.js';
|
|
20
21
|
import { DbHelper } from '../lib/dbHelper.js';
|
|
21
22
|
import { RedisHelper } from '../lib/redisHelper.js';
|
|
22
23
|
import { RedisKeys } from 'befly-shared/redisKeys';
|
|
23
24
|
import { scanAddons, getAddonDir } from 'befly-shared/addonHelper';
|
|
24
|
-
import { scanConfig } from 'befly-shared/scanConfig';
|
|
25
25
|
import { Logger } from '../lib/logger.js';
|
|
26
26
|
import { projectDir } from '../paths.js';
|
|
27
|
+
import { beflyConfig } from '../befly.config.js';
|
|
27
28
|
|
|
28
|
-
import type { SyncMenuOptions, MenuConfig
|
|
29
|
+
import type { SyncMenuOptions, MenuConfig } from '../types/index.js';
|
|
29
30
|
|
|
30
31
|
/**
|
|
31
|
-
*
|
|
32
|
-
*
|
|
33
|
-
* @param transform 路径转换函数
|
|
32
|
+
* 清理目录名中的数字后缀
|
|
33
|
+
* 如:login_1 → login, index_2 → index
|
|
34
34
|
*/
|
|
35
|
-
function
|
|
36
|
-
|
|
37
|
-
menu.path = transform(menu.path);
|
|
38
|
-
}
|
|
39
|
-
menu.children?.forEach((child) => transformMenuPaths(child, transform));
|
|
35
|
+
function cleanDirName(name: string): string {
|
|
36
|
+
return name.replace(/_\d+$/, '');
|
|
40
37
|
}
|
|
41
38
|
|
|
42
39
|
/**
|
|
43
|
-
*
|
|
44
|
-
*
|
|
45
|
-
*
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
* 4. 项目菜单不添加前缀
|
|
40
|
+
* 扫描 views 目录,构建菜单树
|
|
41
|
+
* @param viewsDir views 目录路径
|
|
42
|
+
* @param prefix 路径前缀(addon 前缀)
|
|
43
|
+
* @param parentPath 父级路径
|
|
44
|
+
* @returns 菜单数组
|
|
49
45
|
*/
|
|
50
|
-
function
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
46
|
+
async function scanViewsDir(viewsDir: string, prefix: string, parentPath: string = ''): Promise<MenuConfig[]> {
|
|
47
|
+
if (!existsSync(viewsDir)) {
|
|
48
|
+
return [];
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const menus: MenuConfig[] = [];
|
|
52
|
+
const entries = await readdir(viewsDir, { withFileTypes: true });
|
|
53
|
+
|
|
54
|
+
for (const entry of entries) {
|
|
55
|
+
// 只处理目录,忽略 components 目录
|
|
56
|
+
if (!entry.isDirectory() || entry.name === 'components') {
|
|
57
|
+
continue;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const dirPath = join(viewsDir, entry.name);
|
|
61
|
+
const metaPath = join(dirPath, 'meta.json');
|
|
62
|
+
|
|
63
|
+
// 没有 meta.json 的目录不处理
|
|
64
|
+
if (!existsSync(metaPath)) {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 读取 meta.json
|
|
69
|
+
let meta: { name: string; order?: number };
|
|
70
|
+
try {
|
|
71
|
+
const content = await readFile(metaPath, 'utf-8');
|
|
72
|
+
meta = JSON.parse(content);
|
|
73
|
+
} catch (error: any) {
|
|
74
|
+
Logger.warn({ err: error, path: metaPath }, '读取 meta.json 失败');
|
|
75
|
+
continue;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// 计算路径:清理数字后缀,index 目录特殊处理
|
|
79
|
+
const cleanName = cleanDirName(entry.name);
|
|
80
|
+
let menuPath: string;
|
|
81
|
+
if (cleanName === 'index') {
|
|
82
|
+
// index 目录路径为父级路径,根级别则为 /
|
|
83
|
+
menuPath = parentPath || '/';
|
|
84
|
+
} else {
|
|
85
|
+
menuPath = parentPath ? `${parentPath}/${cleanName}` : `/${cleanName}`;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
// 添加 addon 前缀
|
|
89
|
+
const fullPath = prefix ? `${prefix}${menuPath}` : menuPath;
|
|
90
|
+
|
|
91
|
+
const menu: MenuConfig = {
|
|
92
|
+
name: meta.name,
|
|
93
|
+
path: fullPath,
|
|
94
|
+
sort: meta.order || 100
|
|
95
|
+
};
|
|
96
|
+
|
|
97
|
+
// 递归扫描子目录
|
|
98
|
+
const children = await scanViewsDir(dirPath, prefix, menuPath);
|
|
99
|
+
if (children.length > 0) {
|
|
100
|
+
menu.children = children;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
menus.push(menu);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// 按 sort 排序
|
|
107
|
+
menus.sort((a, b) => (a.sort || 100) - (b.sort || 100));
|
|
108
|
+
|
|
109
|
+
return menus;
|
|
56
110
|
}
|
|
57
111
|
|
|
58
112
|
/**
|
|
59
113
|
* 合并菜单配置
|
|
60
|
-
*
|
|
61
|
-
* 支持三级菜单结构:父级、子级、孙级
|
|
114
|
+
* 支持无限层级菜单结构
|
|
62
115
|
*/
|
|
63
|
-
function mergeMenuConfigs(allMenus: Array<{ menus: MenuConfig[];
|
|
64
|
-
|
|
65
|
-
* 递归合并指定层级的菜单(限制最多3层)
|
|
66
|
-
* @param menus 待合并的菜单数组
|
|
67
|
-
* @param depth 当前深度(1=父级, 2=子级, 3=孙级)
|
|
68
|
-
* @returns 合并后的菜单数组
|
|
69
|
-
*/
|
|
70
|
-
function mergeLevel(menus: MenuConfig[], depth: number = 1): MenuConfig[] {
|
|
71
|
-
const menuMap = new Map<string, MenuConfig>();
|
|
116
|
+
function mergeMenuConfigs(allMenus: Array<{ menus: MenuConfig[]; source: string }>): MenuConfig[] {
|
|
117
|
+
const menuMap = new Map<string, MenuConfig>();
|
|
72
118
|
|
|
119
|
+
for (const { menus } of allMenus) {
|
|
73
120
|
for (const menu of menus) {
|
|
74
121
|
if (!menu.path) continue;
|
|
75
122
|
|
|
76
123
|
const existing = menuMap.get(menu.path);
|
|
77
124
|
if (existing) {
|
|
78
125
|
// 合并子菜单
|
|
79
|
-
if (menu.children
|
|
126
|
+
if (menu.children && menu.children.length > 0) {
|
|
80
127
|
existing.children = existing.children || [];
|
|
81
128
|
existing.children.push(...menu.children);
|
|
82
129
|
}
|
|
@@ -84,73 +131,67 @@ function mergeMenuConfigs(allMenus: Array<{ menus: MenuConfig[]; addonName: stri
|
|
|
84
131
|
menuMap.set(menu.path, { ...menu });
|
|
85
132
|
}
|
|
86
133
|
}
|
|
134
|
+
}
|
|
87
135
|
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
136
|
+
return Array.from(menuMap.values());
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
/**
|
|
140
|
+
* 过滤隐藏的菜单(递归处理子菜单)
|
|
141
|
+
*/
|
|
142
|
+
function filterHiddenMenus(menus: MenuConfig[], hiddenSet: Set<string>): MenuConfig[] {
|
|
143
|
+
const result: MenuConfig[] = [];
|
|
144
|
+
|
|
145
|
+
for (const menu of menus) {
|
|
146
|
+
// 如果菜单在隐藏列表中,跳过
|
|
147
|
+
if (menu.path && hiddenSet.has(menu.path)) {
|
|
148
|
+
continue;
|
|
95
149
|
}
|
|
96
150
|
|
|
97
|
-
|
|
98
|
-
|
|
151
|
+
const filtered = { ...menu };
|
|
152
|
+
|
|
153
|
+
// 递归过滤子菜单
|
|
154
|
+
if (filtered.children && filtered.children.length > 0) {
|
|
155
|
+
filtered.children = filterHiddenMenus(filtered.children, hiddenSet);
|
|
156
|
+
}
|
|
99
157
|
|
|
100
|
-
|
|
101
|
-
|
|
158
|
+
result.push(filtered);
|
|
159
|
+
}
|
|
102
160
|
|
|
103
|
-
return
|
|
161
|
+
return result;
|
|
104
162
|
}
|
|
105
163
|
|
|
106
164
|
/**
|
|
107
|
-
*
|
|
108
|
-
* 子级菜单使用独立路径
|
|
165
|
+
* 收集所有菜单的 path(递归收集所有层级)
|
|
109
166
|
*/
|
|
110
167
|
function collectPaths(menus: MenuConfig[]): Set<string> {
|
|
111
168
|
const paths = new Set<string>();
|
|
112
169
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
paths.add(child.path);
|
|
121
|
-
}
|
|
122
|
-
// 第三层菜单
|
|
123
|
-
if (child.children && child.children.length > 0) {
|
|
124
|
-
for (const grandChild of child.children) {
|
|
125
|
-
if (grandChild.path) {
|
|
126
|
-
paths.add(grandChild.path);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
}
|
|
170
|
+
function collect(items: MenuConfig[]): void {
|
|
171
|
+
for (const menu of items) {
|
|
172
|
+
if (menu.path) {
|
|
173
|
+
paths.add(menu.path);
|
|
174
|
+
}
|
|
175
|
+
if (menu.children && menu.children.length > 0) {
|
|
176
|
+
collect(menu.children);
|
|
130
177
|
}
|
|
131
178
|
}
|
|
132
179
|
}
|
|
133
180
|
|
|
181
|
+
collect(menus);
|
|
134
182
|
return paths;
|
|
135
183
|
}
|
|
136
184
|
|
|
137
185
|
/**
|
|
138
|
-
*
|
|
139
|
-
* @param helper 数据库帮助类
|
|
140
|
-
* @param menu 菜单配置
|
|
141
|
-
* @param pid 父级菜单ID
|
|
142
|
-
* @param existingMenuMap 现有菜单映射
|
|
143
|
-
* @param depth 当前深度(1=父级, 2=子级, 3=孙级)
|
|
144
|
-
* @returns 菜单ID
|
|
186
|
+
* 递归同步单个菜单(无层级限制)
|
|
145
187
|
*/
|
|
146
|
-
async function syncMenuRecursive(helper: any, menu: MenuConfig, pid: number, existingMenuMap: Map<string, any
|
|
188
|
+
async function syncMenuRecursive(helper: any, menu: MenuConfig, pid: number, existingMenuMap: Map<string, any>): Promise<number> {
|
|
147
189
|
const existing = existingMenuMap.get(menu.path || '');
|
|
148
190
|
let menuId: number;
|
|
149
191
|
|
|
150
192
|
if (existing) {
|
|
151
193
|
menuId = existing.id;
|
|
152
194
|
|
|
153
|
-
// 检查是否需要更新
|
|
154
195
|
const needUpdate = existing.pid !== pid || existing.name !== menu.name || existing.sort !== (menu.sort || 0);
|
|
155
196
|
|
|
156
197
|
if (needUpdate) {
|
|
@@ -176,10 +217,9 @@ async function syncMenuRecursive(helper: any, menu: MenuConfig, pid: number, exi
|
|
|
176
217
|
});
|
|
177
218
|
}
|
|
178
219
|
|
|
179
|
-
|
|
180
|
-
if (depth < 3 && menu.children?.length > 0) {
|
|
220
|
+
if (menu.children && menu.children.length > 0) {
|
|
181
221
|
for (const child of menu.children) {
|
|
182
|
-
await syncMenuRecursive(helper, child, menuId, existingMenuMap
|
|
222
|
+
await syncMenuRecursive(helper, child, menuId, existingMenuMap);
|
|
183
223
|
}
|
|
184
224
|
}
|
|
185
225
|
|
|
@@ -187,11 +227,9 @@ async function syncMenuRecursive(helper: any, menu: MenuConfig, pid: number, exi
|
|
|
187
227
|
}
|
|
188
228
|
|
|
189
229
|
/**
|
|
190
|
-
*
|
|
191
|
-
* 子级菜单使用独立路径
|
|
230
|
+
* 同步菜单到数据库
|
|
192
231
|
*/
|
|
193
232
|
async function syncMenus(helper: any, menus: MenuConfig[]): Promise<void> {
|
|
194
|
-
// 批量查询所有现有菜单,建立 path -> menu 的映射
|
|
195
233
|
const allExistingMenus = await helper.getAll({
|
|
196
234
|
table: 'addon_admin_menu',
|
|
197
235
|
fields: ['id', 'pid', 'name', 'path', 'sort']
|
|
@@ -234,52 +272,49 @@ async function deleteObsoleteRecords(helper: any, configPaths: Set<string>): Pro
|
|
|
234
272
|
}
|
|
235
273
|
|
|
236
274
|
/**
|
|
237
|
-
* 加载所有菜单配置(addon +
|
|
238
|
-
* @returns 菜单配置数组
|
|
275
|
+
* 加载所有菜单配置(addon views + 项目 menus.json)
|
|
239
276
|
*/
|
|
240
|
-
async function loadMenuConfigs(): Promise<Array<{ menus: MenuConfig[];
|
|
241
|
-
const allMenus: Array<{ menus: MenuConfig[];
|
|
277
|
+
async function loadMenuConfigs(): Promise<Array<{ menus: MenuConfig[]; source: string }>> {
|
|
278
|
+
const allMenus: Array<{ menus: MenuConfig[]; source: string }> = [];
|
|
242
279
|
|
|
243
|
-
// 1.
|
|
280
|
+
// 1. 扫描所有 addon 的 views 目录
|
|
244
281
|
const addonNames = scanAddons();
|
|
245
282
|
|
|
246
283
|
for (const addonName of addonNames) {
|
|
247
284
|
try {
|
|
248
285
|
const addonDir = getAddonDir(addonName, '');
|
|
249
|
-
const
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
allMenus.push({ menus: menusWithPrefix, addonName: addonName });
|
|
286
|
+
const viewsDir = join(addonDir, 'views');
|
|
287
|
+
|
|
288
|
+
if (existsSync(viewsDir)) {
|
|
289
|
+
const prefix = `/addon/${addonName}`;
|
|
290
|
+
const menus = await scanViewsDir(viewsDir, prefix);
|
|
291
|
+
if (menus.length > 0) {
|
|
292
|
+
allMenus.push({
|
|
293
|
+
menus: menus,
|
|
294
|
+
source: `addon:${addonName}`
|
|
295
|
+
});
|
|
296
|
+
}
|
|
261
297
|
}
|
|
262
298
|
} catch (error: any) {
|
|
263
|
-
Logger.warn({ err: error, addon: addonName }, '
|
|
299
|
+
Logger.warn({ err: error, addon: addonName }, '扫描 addon views 目录失败');
|
|
264
300
|
}
|
|
265
301
|
}
|
|
266
302
|
|
|
267
|
-
// 2.
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
303
|
+
// 2. 读取项目的 menus.json
|
|
304
|
+
const menusJsonPath = join(projectDir, 'menus.json');
|
|
305
|
+
if (existsSync(menusJsonPath)) {
|
|
306
|
+
try {
|
|
307
|
+
const content = await readFile(menusJsonPath, 'utf-8');
|
|
308
|
+
const projectMenus = JSON.parse(content);
|
|
309
|
+
if (Array.isArray(projectMenus) && projectMenus.length > 0) {
|
|
310
|
+
allMenus.push({
|
|
311
|
+
menus: projectMenus,
|
|
312
|
+
source: 'project'
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
} catch (error: any) {
|
|
316
|
+
Logger.warn({ err: error }, '读取项目 menus.json 失败');
|
|
280
317
|
}
|
|
281
|
-
} catch (error: any) {
|
|
282
|
-
Logger.warn({ err: error }, '读取项目配置失败');
|
|
283
318
|
}
|
|
284
319
|
|
|
285
320
|
return allMenus;
|
|
@@ -288,39 +323,46 @@ async function loadMenuConfigs(): Promise<Array<{ menus: MenuConfig[]; addonName
|
|
|
288
323
|
/**
|
|
289
324
|
* SyncMenu 命令主函数
|
|
290
325
|
*/
|
|
291
|
-
export async function syncMenuCommand(
|
|
326
|
+
export async function syncMenuCommand(options: SyncMenuOptions = {}): Promise<void> {
|
|
292
327
|
try {
|
|
293
328
|
if (options.plan) {
|
|
294
329
|
Logger.debug('[计划] 同步菜单配置到数据库(plan 模式不执行)');
|
|
295
330
|
return;
|
|
296
331
|
}
|
|
297
332
|
|
|
298
|
-
// 1.
|
|
333
|
+
// 1. 加载所有菜单配置
|
|
299
334
|
const allMenus = await loadMenuConfigs();
|
|
300
335
|
|
|
301
336
|
// 2. 合并菜单配置
|
|
302
|
-
|
|
337
|
+
let mergedMenus = mergeMenuConfigs(allMenus);
|
|
338
|
+
|
|
339
|
+
// 3. 过滤隐藏菜单(根据 hiddenMenus 配置)
|
|
340
|
+
const hiddenMenus = (beflyConfig as any).hiddenMenus || [];
|
|
341
|
+
if (Array.isArray(hiddenMenus) && hiddenMenus.length > 0) {
|
|
342
|
+
const hiddenSet = new Set(hiddenMenus);
|
|
343
|
+
mergedMenus = filterHiddenMenus(mergedMenus, hiddenSet);
|
|
344
|
+
}
|
|
303
345
|
|
|
304
|
-
//
|
|
305
|
-
await Connect.connect(
|
|
346
|
+
// 连接数据库
|
|
347
|
+
await Connect.connect();
|
|
306
348
|
|
|
307
349
|
const helper = new DbHelper({ redis: new RedisHelper() } as any, Connect.getSql());
|
|
308
350
|
|
|
309
|
-
// 3.
|
|
351
|
+
// 3. 检查表是否存在
|
|
310
352
|
const exists = await helper.tableExists('addon_admin_menu');
|
|
311
353
|
|
|
312
354
|
if (!exists) {
|
|
313
|
-
Logger.debug('表 addon_admin_menu
|
|
355
|
+
Logger.debug('表 addon_admin_menu 不存在,跳过菜单同步');
|
|
314
356
|
return;
|
|
315
357
|
}
|
|
316
358
|
|
|
317
|
-
// 4.
|
|
359
|
+
// 4. 收集所有菜单的 path
|
|
318
360
|
const configPaths = collectPaths(mergedMenus);
|
|
319
361
|
|
|
320
362
|
// 5. 同步菜单
|
|
321
363
|
await syncMenus(helper, mergedMenus);
|
|
322
364
|
|
|
323
|
-
// 6.
|
|
365
|
+
// 6. 删除不存在的菜单
|
|
324
366
|
await deleteObsoleteRecords(helper, configPaths);
|
|
325
367
|
|
|
326
368
|
// 7. 获取最终菜单数据(用于缓存)
|
|
@@ -57,8 +57,6 @@ describe('Integration - JWT + 权限验证', () => {
|
|
|
57
57
|
|
|
58
58
|
describe('Integration - 数据验证 + SQL 构建', () => {
|
|
59
59
|
test('API 请求:验证数据 + 构建查询', () => {
|
|
60
|
-
const validator = new Validator();
|
|
61
|
-
|
|
62
60
|
// 1. 验证用户输入
|
|
63
61
|
const userData = {
|
|
64
62
|
email: 'test@example.com',
|
|
@@ -72,7 +70,7 @@ describe('Integration - 数据验证 + SQL 构建', () => {
|
|
|
72
70
|
username: { name: '用户名', type: 'string', min: 2, max: 20 }
|
|
73
71
|
};
|
|
74
72
|
|
|
75
|
-
const validationResult =
|
|
73
|
+
const validationResult = Validator.validate(userData, rules, ['email', 'username']);
|
|
76
74
|
expect(validationResult.code).toBe(0);
|
|
77
75
|
|
|
78
76
|
// 2. 验证通过后构建 SQL 查询
|
|
@@ -86,8 +84,6 @@ describe('Integration - 数据验证 + SQL 构建', () => {
|
|
|
86
84
|
});
|
|
87
85
|
|
|
88
86
|
test('数据插入:验证 + 字段转换 + SQL 构建', () => {
|
|
89
|
-
const validator = new Validator();
|
|
90
|
-
|
|
91
87
|
// 1. 验证数据
|
|
92
88
|
const newUser = {
|
|
93
89
|
userName: 'jane',
|
|
@@ -101,7 +97,7 @@ describe('Integration - 数据验证 + SQL 构建', () => {
|
|
|
101
97
|
userAge: { name: '年龄', type: 'number', min: 0, max: 150 }
|
|
102
98
|
};
|
|
103
99
|
|
|
104
|
-
const validationResult =
|
|
100
|
+
const validationResult = Validator.validate(newUser, rules, ['userName', 'userEmail']);
|
|
105
101
|
expect(validationResult.code).toBe(0);
|
|
106
102
|
|
|
107
103
|
// 2. 字段转换(驼峰转下划线)
|
|
@@ -6,7 +6,6 @@
|
|
|
6
6
|
import { describe, expect, it, test, beforeAll, afterAll } from 'bun:test';
|
|
7
7
|
import { RedisClient } from 'bun';
|
|
8
8
|
|
|
9
|
-
import { defaultOptions } from '../config.js';
|
|
10
9
|
import { Connect } from '../lib/connect.js';
|
|
11
10
|
import { RedisHelper } from '../lib/redisHelper.js';
|
|
12
11
|
|
|
@@ -14,7 +13,7 @@ let redis: RedisHelper;
|
|
|
14
13
|
|
|
15
14
|
beforeAll(async () => {
|
|
16
15
|
// 连接 Redis
|
|
17
|
-
await Connect.connectRedis(
|
|
16
|
+
await Connect.connectRedis();
|
|
18
17
|
redis = new RedisHelper();
|
|
19
18
|
});
|
|
20
19
|
|