befly 2.3.2 → 3.0.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/apis/health/info.ts +64 -0
- package/apis/tool/tokenCheck.ts +51 -0
- package/bin/befly.ts +202 -0
- package/checks/conflict.ts +408 -0
- package/checks/table.ts +284 -0
- package/config/env.ts +218 -0
- package/config/reserved.ts +96 -0
- package/main.ts +101 -0
- package/package.json +45 -16
- package/plugins/{db.js → db.ts} +25 -12
- package/plugins/logger.ts +28 -0
- package/plugins/redis.ts +51 -0
- package/plugins/tool.ts +34 -0
- package/scripts/syncDb/apply.ts +171 -0
- package/scripts/syncDb/constants.ts +70 -0
- package/scripts/syncDb/ddl.ts +182 -0
- package/scripts/syncDb/helpers.ts +172 -0
- package/scripts/syncDb/index.ts +215 -0
- package/scripts/syncDb/schema.ts +199 -0
- package/scripts/syncDb/sqlite.ts +50 -0
- package/scripts/syncDb/state.ts +104 -0
- package/scripts/syncDb/table.ts +204 -0
- package/scripts/syncDb/tableCreate.ts +142 -0
- package/scripts/syncDb/tests/constants.test.ts +104 -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 +9 -0
- package/scripts/syncDev.ts +112 -0
- package/system.ts +149 -0
- package/tables/_common.json +21 -0
- package/tables/admin.json +10 -0
- package/tsconfig.json +58 -0
- package/types/api.d.ts +246 -0
- package/types/befly.d.ts +234 -0
- package/types/common.d.ts +215 -0
- package/types/context.ts +167 -0
- package/types/crypto.d.ts +23 -0
- package/types/database.d.ts +278 -0
- package/types/index.d.ts +16 -0
- package/types/index.ts +459 -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 +44 -0
- package/types/tool.d.ts +67 -0
- package/types/validator.d.ts +45 -0
- package/utils/addonHelper.ts +60 -0
- package/utils/api.ts +23 -0
- package/utils/{colors.js → colors.ts} +79 -21
- package/utils/crypto.ts +308 -0
- package/utils/datetime.ts +51 -0
- package/utils/dbHelper.ts +142 -0
- package/utils/errorHandler.ts +68 -0
- package/utils/index.ts +46 -0
- package/utils/jwt.ts +493 -0
- package/utils/logger.ts +284 -0
- package/utils/objectHelper.ts +68 -0
- package/utils/pluginHelper.ts +62 -0
- package/utils/redisHelper.ts +338 -0
- package/utils/response.ts +38 -0
- package/utils/{sqlBuilder.js → sqlBuilder.ts} +233 -97
- package/utils/sqlHelper.ts +447 -0
- package/utils/tableHelper.ts +167 -0
- package/utils/tool.ts +230 -0
- package/utils/typeHelper.ts +101 -0
- package/utils/validate.ts +451 -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/checks/table.js +0 -221
- package/config/env.js +0 -62
- package/main.js +0 -579
- package/plugins/logger.js +0 -14
- package/plugins/redis.js +0 -32
- package/plugins/tool.js +0 -8
- package/scripts/syncDb.js +0 -603
- 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/crypto.js +0 -260
- package/utils/index.js +0 -387
- package/utils/jwt.js +0 -387
- package/utils/logger.js +0 -143
- package/utils/redisHelper.js +0 -74
- package/utils/sqlManager.js +0 -471
- package/utils/tool.js +0 -31
- package/utils/validate.js +0 -228
package/utils/{xml.js → xml.ts}
RENAMED
|
@@ -1,39 +1,66 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* XML 解析器
|
|
2
|
+
* XML 解析器 - TypeScript 版本
|
|
3
3
|
* 将 XML 字符串解析为 JSON 对象
|
|
4
4
|
*/
|
|
5
|
-
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* XML 解析选项
|
|
8
|
+
*/
|
|
9
|
+
interface XmlParseOptions {
|
|
10
|
+
/** 是否忽略属性 */
|
|
11
|
+
ignoreAttributes?: boolean;
|
|
12
|
+
/** 属性前缀 */
|
|
13
|
+
attributePrefix?: string;
|
|
14
|
+
/** 文本内容的键名 */
|
|
15
|
+
textKey?: string;
|
|
16
|
+
/** 是否去除首尾空格 */
|
|
17
|
+
trimValues?: boolean;
|
|
18
|
+
/** 是否解析布尔值 */
|
|
19
|
+
parseBooleans?: boolean;
|
|
20
|
+
/** 是否解析数字 */
|
|
21
|
+
parseNumbers?: boolean;
|
|
22
|
+
/** 是否使用自定义解析器 */
|
|
23
|
+
customParser?: boolean;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* 解析结果接口(内部使用)
|
|
28
|
+
*/
|
|
29
|
+
interface ParseResult {
|
|
30
|
+
value: Record<string, any>;
|
|
31
|
+
end: number;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* XML 解析器类
|
|
36
|
+
*/
|
|
37
|
+
export class Xml {
|
|
38
|
+
private options: Required<XmlParseOptions>;
|
|
39
|
+
|
|
6
40
|
/**
|
|
7
41
|
* 构造函数
|
|
8
|
-
* @param
|
|
42
|
+
* @param options - 解析选项
|
|
9
43
|
*/
|
|
10
|
-
constructor(options = {}) {
|
|
44
|
+
constructor(options: XmlParseOptions = {}) {
|
|
11
45
|
// 默认配置
|
|
12
46
|
this.options = {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
parseBooleans: true, // 是否解析布尔值
|
|
21
|
-
parseNumbers: true, // 是否解析数字
|
|
22
|
-
|
|
23
|
-
// 高级配置
|
|
24
|
-
customParser: true, // 是否使用自定义解析器
|
|
25
|
-
|
|
26
|
-
// 合并用户选项
|
|
47
|
+
ignoreAttributes: false,
|
|
48
|
+
attributePrefix: '@',
|
|
49
|
+
textKey: '#text',
|
|
50
|
+
trimValues: true,
|
|
51
|
+
parseBooleans: true,
|
|
52
|
+
parseNumbers: true,
|
|
53
|
+
customParser: true,
|
|
27
54
|
...options
|
|
28
55
|
};
|
|
29
56
|
}
|
|
30
57
|
|
|
31
58
|
/**
|
|
32
59
|
* 解析 XML 字符串为 JSON 对象
|
|
33
|
-
* @param
|
|
34
|
-
* @returns
|
|
60
|
+
* @param xmlData - XML 字符串
|
|
61
|
+
* @returns 解析后的 JSON 对象
|
|
35
62
|
*/
|
|
36
|
-
parse(xmlData) {
|
|
63
|
+
parse(xmlData: string): Record<string, any> {
|
|
37
64
|
if (typeof xmlData !== 'string') {
|
|
38
65
|
throw new Error('无效的 XML 数据');
|
|
39
66
|
}
|
|
@@ -54,20 +81,22 @@ class Xml {
|
|
|
54
81
|
|
|
55
82
|
// 自定义解析
|
|
56
83
|
if (this.options.customParser) {
|
|
57
|
-
return this
|
|
84
|
+
return this.customParse(xmlData);
|
|
58
85
|
}
|
|
86
|
+
|
|
87
|
+
return {};
|
|
59
88
|
}
|
|
60
89
|
|
|
61
90
|
/**
|
|
62
91
|
* 解析属性字符串
|
|
63
|
-
* @param
|
|
64
|
-
* @param
|
|
92
|
+
* @param attributesStr - 属性字符串
|
|
93
|
+
* @param element - 元素对象
|
|
65
94
|
*/
|
|
66
|
-
|
|
95
|
+
private parseAttributes(attributesStr: string, element: Record<string, any>): void {
|
|
67
96
|
// 移除标签名,只保留属性部分
|
|
68
97
|
const attrPart = attributesStr.replace(/^\w+\s*/, '');
|
|
69
98
|
const attrRegex = /(\w+)=["']([^"']*)["']/g;
|
|
70
|
-
let match;
|
|
99
|
+
let match: RegExpExecArray | null;
|
|
71
100
|
|
|
72
101
|
while ((match = attrRegex.exec(attrPart)) !== null) {
|
|
73
102
|
const attrName = this.options.attributePrefix + match[1];
|
|
@@ -78,18 +107,18 @@ class Xml {
|
|
|
78
107
|
element[attrName] = attrValue;
|
|
79
108
|
} else {
|
|
80
109
|
// 其他属性进行类型解析
|
|
81
|
-
element[attrName] = this
|
|
110
|
+
element[attrName] = this.parseValue(attrValue);
|
|
82
111
|
}
|
|
83
112
|
}
|
|
84
113
|
}
|
|
85
114
|
|
|
86
115
|
/**
|
|
87
116
|
* 自定义解析方法
|
|
88
|
-
* @param
|
|
89
|
-
* @returns
|
|
117
|
+
* @param xmlData - XML 数据
|
|
118
|
+
* @returns 解析结果
|
|
90
119
|
*/
|
|
91
|
-
|
|
92
|
-
const result = this
|
|
120
|
+
private customParse(xmlData: string): Record<string, any> {
|
|
121
|
+
const result = this.parseXmlElement(xmlData, 0).value;
|
|
93
122
|
|
|
94
123
|
// 如果结果只有一个根元素,返回该元素的内容
|
|
95
124
|
const keys = Object.keys(result);
|
|
@@ -101,11 +130,11 @@ class Xml {
|
|
|
101
130
|
|
|
102
131
|
/**
|
|
103
132
|
* 解析 XML 元素
|
|
104
|
-
* @param
|
|
105
|
-
* @param
|
|
106
|
-
* @returns
|
|
133
|
+
* @param xml - XML 字符串
|
|
134
|
+
* @param start - 开始位置
|
|
135
|
+
* @returns 包含解析结果和结束位置的对象
|
|
107
136
|
*/
|
|
108
|
-
|
|
137
|
+
private parseXmlElement(xml: string, start: number): ParseResult {
|
|
109
138
|
const tagStart = xml.indexOf('<', start);
|
|
110
139
|
if (tagStart === -1) {
|
|
111
140
|
return { value: {}, end: xml.length };
|
|
@@ -117,13 +146,13 @@ class Xml {
|
|
|
117
146
|
}
|
|
118
147
|
|
|
119
148
|
const fullTag = xml.slice(tagStart + 1, tagEnd);
|
|
120
|
-
const element = {};
|
|
149
|
+
const element: Record<string, any> = {};
|
|
121
150
|
|
|
122
151
|
// 自闭合标签
|
|
123
152
|
if (fullTag.endsWith('/')) {
|
|
124
153
|
const tagName = fullTag.slice(0, -1).split(/\s+/)[0];
|
|
125
154
|
if (!this.options.ignoreAttributes && fullTag.includes(' ')) {
|
|
126
|
-
this
|
|
155
|
+
this.parseAttributes(fullTag.slice(0, -1), element);
|
|
127
156
|
}
|
|
128
157
|
return { value: { [tagName]: Object.keys(element).length > 0 ? element : '' }, end: tagEnd + 1 };
|
|
129
158
|
}
|
|
@@ -134,7 +163,7 @@ class Xml {
|
|
|
134
163
|
if (commentEnd === -1) {
|
|
135
164
|
throw new Error('格式错误:未找到注释结束符');
|
|
136
165
|
}
|
|
137
|
-
return this
|
|
166
|
+
return this.parseXmlElement(xml, commentEnd + 3);
|
|
138
167
|
}
|
|
139
168
|
|
|
140
169
|
// CDATA
|
|
@@ -143,15 +172,14 @@ class Xml {
|
|
|
143
172
|
if (cdataEnd === -1) {
|
|
144
173
|
throw new Error('格式错误:未找到 CDATA 结束符');
|
|
145
174
|
}
|
|
146
|
-
|
|
147
|
-
return this.#parseXmlElement(xml, cdataEnd + 3);
|
|
175
|
+
return this.parseXmlElement(xml, cdataEnd + 3);
|
|
148
176
|
}
|
|
149
177
|
|
|
150
178
|
const tagName = fullTag.split(/\s+/)[0];
|
|
151
179
|
|
|
152
180
|
// 解析属性
|
|
153
181
|
if (!this.options.ignoreAttributes && fullTag.includes(' ')) {
|
|
154
|
-
this
|
|
182
|
+
this.parseAttributes(fullTag, element);
|
|
155
183
|
}
|
|
156
184
|
|
|
157
185
|
// 查找结束标签
|
|
@@ -171,7 +199,7 @@ class Xml {
|
|
|
171
199
|
|
|
172
200
|
// 如果没有子标签,直接解析文本
|
|
173
201
|
if (!content.includes('<')) {
|
|
174
|
-
const textValue = this
|
|
202
|
+
const textValue = this.parseValue(content);
|
|
175
203
|
if (Object.keys(element).length === 0) {
|
|
176
204
|
return { value: { [tagName]: textValue }, end: endTagStart + endTag.length };
|
|
177
205
|
} else {
|
|
@@ -181,7 +209,7 @@ class Xml {
|
|
|
181
209
|
} else {
|
|
182
210
|
// 解析子元素和混合内容
|
|
183
211
|
let pos = 0;
|
|
184
|
-
const texts = [];
|
|
212
|
+
const texts: string[] = [];
|
|
185
213
|
|
|
186
214
|
while (pos < content.length) {
|
|
187
215
|
const nextTag = content.indexOf('<', pos);
|
|
@@ -200,12 +228,12 @@ class Xml {
|
|
|
200
228
|
}
|
|
201
229
|
|
|
202
230
|
// 解析子元素
|
|
203
|
-
const childResult = this
|
|
231
|
+
const childResult = this.parseXmlElement(content, nextTag);
|
|
204
232
|
const childObj = childResult.value;
|
|
205
233
|
|
|
206
234
|
// 合并子元素到当前元素
|
|
207
235
|
for (const [key, value] of Object.entries(childObj)) {
|
|
208
|
-
this
|
|
236
|
+
this.addElement(element, key, value);
|
|
209
237
|
}
|
|
210
238
|
|
|
211
239
|
pos = childResult.end;
|
|
@@ -215,9 +243,9 @@ class Xml {
|
|
|
215
243
|
if (texts.length > 0) {
|
|
216
244
|
const combinedText = texts.join(' ');
|
|
217
245
|
if (Object.keys(element).length === 0) {
|
|
218
|
-
return { value: { [tagName]: this
|
|
246
|
+
return { value: { [tagName]: this.parseValue(combinedText) }, end: endTagStart + endTag.length };
|
|
219
247
|
} else {
|
|
220
|
-
element[this.options.textKey] = this
|
|
248
|
+
element[this.options.textKey] = this.parseValue(combinedText);
|
|
221
249
|
}
|
|
222
250
|
}
|
|
223
251
|
|
|
@@ -227,11 +255,11 @@ class Xml {
|
|
|
227
255
|
|
|
228
256
|
/**
|
|
229
257
|
* 添加元素到对象
|
|
230
|
-
* @param
|
|
231
|
-
* @param
|
|
232
|
-
* @param
|
|
258
|
+
* @param parent - 父对象
|
|
259
|
+
* @param name - 元素名
|
|
260
|
+
* @param value - 元素值
|
|
233
261
|
*/
|
|
234
|
-
|
|
262
|
+
private addElement(parent: Record<string, any>, name: string, value: any): void {
|
|
235
263
|
if (parent[name] === undefined) {
|
|
236
264
|
parent[name] = value;
|
|
237
265
|
} else if (Array.isArray(parent[name])) {
|
|
@@ -243,10 +271,10 @@ class Xml {
|
|
|
243
271
|
|
|
244
272
|
/**
|
|
245
273
|
* 解析值的类型
|
|
246
|
-
* @param
|
|
247
|
-
* @returns
|
|
274
|
+
* @param value - 原始值
|
|
275
|
+
* @returns 解析后的值
|
|
248
276
|
*/
|
|
249
|
-
|
|
277
|
+
private parseValue(value: string): string | number | boolean {
|
|
250
278
|
if (!value || typeof value !== 'string') {
|
|
251
279
|
return value;
|
|
252
280
|
}
|
|
@@ -288,16 +316,16 @@ class Xml {
|
|
|
288
316
|
}
|
|
289
317
|
|
|
290
318
|
// 处理 XML 实体
|
|
291
|
-
return this
|
|
319
|
+
return this.decodeEntities(value);
|
|
292
320
|
}
|
|
293
321
|
|
|
294
322
|
/**
|
|
295
323
|
* 解码 XML 实体
|
|
296
|
-
* @param
|
|
297
|
-
* @returns
|
|
324
|
+
* @param value - 包含实体的字符串
|
|
325
|
+
* @returns 解码后的字符串
|
|
298
326
|
*/
|
|
299
|
-
|
|
300
|
-
const entities = {
|
|
327
|
+
private decodeEntities(value: string): string {
|
|
328
|
+
const entities: Record<string, string> = {
|
|
301
329
|
'&': '&',
|
|
302
330
|
'<': '<',
|
|
303
331
|
'>': '>',
|
|
@@ -312,39 +340,39 @@ class Xml {
|
|
|
312
340
|
|
|
313
341
|
/**
|
|
314
342
|
* 静态方法:快速解析 XML
|
|
315
|
-
* @param
|
|
316
|
-
* @param
|
|
317
|
-
* @returns
|
|
343
|
+
* @param xmlData - XML 数据
|
|
344
|
+
* @param options - 解析选项
|
|
345
|
+
* @returns 解析后的 JSON 对象
|
|
318
346
|
*/
|
|
319
|
-
static parse(xmlData, options = {}) {
|
|
347
|
+
static parse(xmlData: string, options: XmlParseOptions = {}): Record<string, any> {
|
|
320
348
|
const parser = new Xml(options);
|
|
321
349
|
return parser.parse(xmlData);
|
|
322
350
|
}
|
|
323
351
|
|
|
324
352
|
/**
|
|
325
353
|
* 静态方法:解析 XML 并保留属性
|
|
326
|
-
* @param
|
|
327
|
-
* @returns
|
|
354
|
+
* @param xmlData - XML 数据
|
|
355
|
+
* @returns 解析后的 JSON 对象
|
|
328
356
|
*/
|
|
329
|
-
static parseWithAttributes(xmlData) {
|
|
357
|
+
static parseWithAttributes(xmlData: string): Record<string, any> {
|
|
330
358
|
return Xml.parse(xmlData, { ignoreAttributes: false });
|
|
331
359
|
}
|
|
332
360
|
|
|
333
361
|
/**
|
|
334
362
|
* 静态方法:解析 XML 并忽略属性
|
|
335
|
-
* @param
|
|
336
|
-
* @returns
|
|
363
|
+
* @param xmlData - XML 数据
|
|
364
|
+
* @returns 解析后的 JSON 对象
|
|
337
365
|
*/
|
|
338
|
-
static parseIgnoreAttributes(xmlData) {
|
|
366
|
+
static parseIgnoreAttributes(xmlData: string): Record<string, any> {
|
|
339
367
|
return Xml.parse(xmlData, { ignoreAttributes: true });
|
|
340
368
|
}
|
|
341
369
|
|
|
342
370
|
/**
|
|
343
371
|
* 静态方法:验证 XML 格式
|
|
344
|
-
* @param
|
|
345
|
-
* @returns
|
|
372
|
+
* @param xmlData - XML 数据
|
|
373
|
+
* @returns 是否有效
|
|
346
374
|
*/
|
|
347
|
-
static validate(xmlData) {
|
|
375
|
+
static validate(xmlData: string): boolean {
|
|
348
376
|
try {
|
|
349
377
|
Xml.parse(xmlData);
|
|
350
378
|
return true;
|
|
@@ -353,5 +381,3 @@ class Xml {
|
|
|
353
381
|
}
|
|
354
382
|
}
|
|
355
383
|
}
|
|
356
|
-
|
|
357
|
-
export { Xml };
|
package/.npmrc
DELETED
package/.prettierignore
DELETED
package/.prettierrc
DELETED
package/apis/health/info.js
DELETED
|
@@ -1,49 +0,0 @@
|
|
|
1
|
-
import { Env } from '../../config/env.js';
|
|
2
|
-
import { Api } from '../../utils/api.js';
|
|
3
|
-
import { RYes, RNo } from '../../utils/index.js';
|
|
4
|
-
|
|
5
|
-
export default Api.POST(
|
|
6
|
-
//
|
|
7
|
-
'健康检查',
|
|
8
|
-
false,
|
|
9
|
-
{},
|
|
10
|
-
[],
|
|
11
|
-
async (befly, ctx) => {
|
|
12
|
-
try {
|
|
13
|
-
const info = {
|
|
14
|
-
status: 'ok',
|
|
15
|
-
timestamp: new Date().toISOString(),
|
|
16
|
-
uptime: process.uptime(),
|
|
17
|
-
memory: process.memoryUsage(),
|
|
18
|
-
runtime: 'Bun',
|
|
19
|
-
version: Bun.version,
|
|
20
|
-
platform: process.platform,
|
|
21
|
-
arch: process.arch
|
|
22
|
-
};
|
|
23
|
-
// 检查 Redis 连接状态
|
|
24
|
-
if (Env.REDIS_ENABLE === 1) {
|
|
25
|
-
if (befly.redis) {
|
|
26
|
-
try {
|
|
27
|
-
await befly.redis.ping();
|
|
28
|
-
info.redis = '已连接';
|
|
29
|
-
} catch (error) {
|
|
30
|
-
info.redis = '未连接';
|
|
31
|
-
info.redisError = error.message;
|
|
32
|
-
}
|
|
33
|
-
} else {
|
|
34
|
-
info.redis = '未开启';
|
|
35
|
-
}
|
|
36
|
-
} else {
|
|
37
|
-
info.redis = '禁用';
|
|
38
|
-
}
|
|
39
|
-
return RYes('健康检查成功', info);
|
|
40
|
-
} catch (error) {
|
|
41
|
-
befly.logger.error({
|
|
42
|
-
msg: '健康检查失败',
|
|
43
|
-
error: error.message,
|
|
44
|
-
stack: error.stack
|
|
45
|
-
});
|
|
46
|
-
return RNo('健康检查失败');
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
);
|
package/apis/tool/tokenCheck.js
DELETED
|
@@ -1,29 +0,0 @@
|
|
|
1
|
-
import { Env } from '../../config/env.js';
|
|
2
|
-
import { Api } from '../../utils/api.js';
|
|
3
|
-
import { RYes, RNo } from '../../utils/index.js';
|
|
4
|
-
import { Jwt } from '../../utils/jwt.js';
|
|
5
|
-
|
|
6
|
-
export default Api.POST(
|
|
7
|
-
//
|
|
8
|
-
'令牌检测',
|
|
9
|
-
false,
|
|
10
|
-
{},
|
|
11
|
-
[],
|
|
12
|
-
async (befly, ctx) => {
|
|
13
|
-
try {
|
|
14
|
-
const token = ctx.headers?.authorization?.split(' ')[1] || '';
|
|
15
|
-
if (!token) {
|
|
16
|
-
return RNo('令牌不能为空');
|
|
17
|
-
}
|
|
18
|
-
const jwtData = await Jwt.verify(token);
|
|
19
|
-
return RYes('令牌有效');
|
|
20
|
-
} catch (error) {
|
|
21
|
-
befly.logger.error({
|
|
22
|
-
msg: '令牌检测失败',
|
|
23
|
-
error: error.message,
|
|
24
|
-
stack: error.stack
|
|
25
|
-
});
|
|
26
|
-
return RNo('令牌检测失败');
|
|
27
|
-
}
|
|
28
|
-
}
|
|
29
|
-
);
|
package/checks/table.js
DELETED
|
@@ -1,221 +0,0 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
|
-
import { Logger } from '../utils/logger.js';
|
|
3
|
-
import { parseFieldRule, validateFieldName, validateFieldType, validateMinMax, validateDefaultValue, validateIndex, validateRegex } from '../utils/index.js';
|
|
4
|
-
import { __dirtables, getProjectDir } from '../system.js';
|
|
5
|
-
|
|
6
|
-
// 所有校验函数均复用 utils/index.js 导出的实现
|
|
7
|
-
|
|
8
|
-
export const checkTable = async () => {
|
|
9
|
-
try {
|
|
10
|
-
const tablesGlob = new Bun.Glob('*.json');
|
|
11
|
-
|
|
12
|
-
// 统计信息
|
|
13
|
-
let totalFiles = 0;
|
|
14
|
-
let totalRules = 0;
|
|
15
|
-
let validFiles = 0;
|
|
16
|
-
let invalidFiles = 0;
|
|
17
|
-
|
|
18
|
-
// 收集所有表文件
|
|
19
|
-
const allTableFiles = [];
|
|
20
|
-
const coreTableNames = new Set(); // 存储内核表文件名
|
|
21
|
-
|
|
22
|
-
// 收集内核表字段定义文件
|
|
23
|
-
for await (const file of tablesGlob.scan({
|
|
24
|
-
cwd: __dirtables,
|
|
25
|
-
absolute: true,
|
|
26
|
-
onlyFiles: true
|
|
27
|
-
})) {
|
|
28
|
-
const fileName = path.basename(file, '.json');
|
|
29
|
-
coreTableNames.add(fileName);
|
|
30
|
-
allTableFiles.push({ file, type: 'core' });
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
// 收集项目表字段定义文件,并检查是否与内核表同名
|
|
34
|
-
for await (const file of tablesGlob.scan({
|
|
35
|
-
cwd: getProjectDir('tables'),
|
|
36
|
-
absolute: true,
|
|
37
|
-
onlyFiles: true
|
|
38
|
-
})) {
|
|
39
|
-
const fileName = path.basename(file, '.json');
|
|
40
|
-
|
|
41
|
-
// 检查项目表是否与内核表同名
|
|
42
|
-
if (coreTableNames.has(fileName)) {
|
|
43
|
-
Logger.error(`项目表 ${fileName}.json 与内核表同名,项目表不能与内核表定义文件同名`);
|
|
44
|
-
invalidFiles++;
|
|
45
|
-
totalFiles++;
|
|
46
|
-
continue;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
allTableFiles.push({ file, type: 'project' });
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// 保留字段列表
|
|
53
|
-
const reservedFields = ['id', 'created_at', 'updated_at', 'deleted_at', 'state'];
|
|
54
|
-
|
|
55
|
-
// 合并进行验证逻辑
|
|
56
|
-
for (const { file, type } of allTableFiles) {
|
|
57
|
-
totalFiles++;
|
|
58
|
-
const fileName = path.basename(file);
|
|
59
|
-
const fileBaseName = path.basename(file, '.json');
|
|
60
|
-
const fileType = type === 'core' ? '内核' : '项目';
|
|
61
|
-
|
|
62
|
-
try {
|
|
63
|
-
// 1) 文件名小驼峰校验:必须以小写字母开头,后续可包含小写/数字,或多个 [大写+小写/数字] 片段
|
|
64
|
-
// 示例:userTable、testCustomers、common
|
|
65
|
-
const lowerCamelCaseRegex = /^[a-z][a-z0-9]*(?:[A-Z][a-z0-9]*)*$/;
|
|
66
|
-
if (!lowerCamelCaseRegex.test(fileBaseName)) {
|
|
67
|
-
Logger.error(`${fileType}表 ${fileName} 文件名必须使用小驼峰命名(例如 testCustomers.json)`);
|
|
68
|
-
// 命名不合规,直接标记为无效文件
|
|
69
|
-
throw new Error('文件命名不符合小驼峰规范');
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
// 读取并解析 JSON 文件
|
|
73
|
-
const table = await Bun.file(file).json();
|
|
74
|
-
let fileValid = true;
|
|
75
|
-
let fileRules = 0;
|
|
76
|
-
|
|
77
|
-
// 检查 table 中的每个验证规则
|
|
78
|
-
for (const [fieldName, rule] of Object.entries(table)) {
|
|
79
|
-
fileRules++;
|
|
80
|
-
totalRules++;
|
|
81
|
-
|
|
82
|
-
// 检查是否使用了保留字段
|
|
83
|
-
if (reservedFields.includes(fieldName)) {
|
|
84
|
-
Logger.error(`${fileType}表 ${fileName} 文件包含保留字段 ${fieldName},不能在表定义中使用以下字段: ${reservedFields.join(', ')}`);
|
|
85
|
-
fileValid = false;
|
|
86
|
-
continue;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
// 验证规则格式
|
|
90
|
-
try {
|
|
91
|
-
const ruleParts = parseFieldRule(rule);
|
|
92
|
-
|
|
93
|
-
if (ruleParts.length !== 7) {
|
|
94
|
-
Logger.warn(`${fileType}表 ${fileName} 文件 ${fieldName} 验证规则错误,应包含 7 个部分,但包含 ${ruleParts.length} 个部分`);
|
|
95
|
-
fileValid = false;
|
|
96
|
-
continue;
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
const [name, type, minStr, maxStr, defaultValue, isIndexStr, regexConstraint] = ruleParts;
|
|
100
|
-
|
|
101
|
-
// 使用新的验证函数进行严格验证
|
|
102
|
-
// 第1个值:名称必须为中文、数字、字母
|
|
103
|
-
if (!validateFieldName(name)) {
|
|
104
|
-
Logger.error(`${fileType}表 ${fileName} 文件 ${fieldName} 名称 "${name}" 格式错误,必须为中文、数字、字母`);
|
|
105
|
-
fileValid = false;
|
|
106
|
-
continue;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
// 第2个值:字段类型必须为string,number,text,array之一
|
|
110
|
-
if (!validateFieldType(type)) {
|
|
111
|
-
Logger.error(`${fileType}表 ${fileName} 文件 ${fieldName} 类型 "${type}" 格式错误,必须为string、number、text、array之一`);
|
|
112
|
-
fileValid = false;
|
|
113
|
-
continue;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
// 第3个值:最小值必须为null或数字
|
|
117
|
-
if (!validateMinMax(minStr)) {
|
|
118
|
-
Logger.error(`${fileType}表 ${fileName} 文件 ${fieldName} 最小值 "${minStr}" 格式错误,必须为null或数字`);
|
|
119
|
-
fileValid = false;
|
|
120
|
-
continue;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
// 第4个值与类型联动校验
|
|
124
|
-
if (type === 'text') {
|
|
125
|
-
// text 类型:不允许设置最小/最大长度与默认值(均需为 null)
|
|
126
|
-
if (minStr !== 'null') {
|
|
127
|
-
Logger.error(`${fileType}表 ${fileName} 文件 ${fieldName} 的 text 类型最小值必须为 null,当前为 "${minStr}"`);
|
|
128
|
-
fileValid = false;
|
|
129
|
-
continue;
|
|
130
|
-
}
|
|
131
|
-
if (maxStr !== 'null') {
|
|
132
|
-
Logger.error(`${fileType}表 ${fileName} 文件 ${fieldName} 的 text 类型最大长度必须为 null,当前为 "${maxStr}"`);
|
|
133
|
-
fileValid = false;
|
|
134
|
-
continue;
|
|
135
|
-
}
|
|
136
|
-
} else if (type === 'string') {
|
|
137
|
-
// string:最大长度必为具体数字,且 1..65535
|
|
138
|
-
if (maxStr === 'null' || !validateMinMax(maxStr)) {
|
|
139
|
-
Logger.error(`${fileType}表 ${fileName} 文件 ${fieldName} 最大长度 "${maxStr}" 格式错误,string 类型必须为具体数字`);
|
|
140
|
-
fileValid = false;
|
|
141
|
-
continue;
|
|
142
|
-
}
|
|
143
|
-
const maxVal = parseInt(maxStr, 10);
|
|
144
|
-
if (!(maxVal > 0 && maxVal <= 65535)) {
|
|
145
|
-
Logger.error(`${fileType}表 ${fileName} 文件 ${fieldName} 最大长度 ${maxStr} 越界,string 类型长度必须在 1..65535 范围内`);
|
|
146
|
-
fileValid = false;
|
|
147
|
-
continue;
|
|
148
|
-
}
|
|
149
|
-
} else if (type === 'array') {
|
|
150
|
-
// array:最大长度必为数字(用于JSON字符串长度限制)
|
|
151
|
-
if (maxStr === 'null' || !validateMinMax(maxStr)) {
|
|
152
|
-
Logger.error(`${fileType}表 ${fileName} 文件 ${fieldName} 最大长度 "${maxStr}" 格式错误,array 类型必须为具体数字`);
|
|
153
|
-
fileValid = false;
|
|
154
|
-
continue;
|
|
155
|
-
}
|
|
156
|
-
} else {
|
|
157
|
-
// number 等其他:允许 null 或数字
|
|
158
|
-
if (!validateMinMax(maxStr)) {
|
|
159
|
-
Logger.error(`${fileType}表 ${fileName} 文件 ${fieldName} 最大值 "${maxStr}" 格式错误,必须为null或数字`);
|
|
160
|
-
fileValid = false;
|
|
161
|
-
continue;
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
// 第5个值:默认值校验
|
|
166
|
-
if (type === 'text') {
|
|
167
|
-
// text 不允许默认值
|
|
168
|
-
if (defaultValue !== 'null') {
|
|
169
|
-
Logger.error(`${fileType}表 ${fileName} 文件 ${fieldName} 为 text 类型,默认值必须为 null,当前为 "${defaultValue}"`);
|
|
170
|
-
fileValid = false;
|
|
171
|
-
continue;
|
|
172
|
-
}
|
|
173
|
-
} else {
|
|
174
|
-
if (!validateDefaultValue(defaultValue)) {
|
|
175
|
-
Logger.error(`${fileType}表 ${fileName} 文件 ${fieldName} 默认值 "${defaultValue}" 格式错误,必须为null、字符串或数字`);
|
|
176
|
-
fileValid = false;
|
|
177
|
-
continue;
|
|
178
|
-
}
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
// 第6个值:是否创建索引必须为0或1
|
|
182
|
-
if (!validateIndex(isIndexStr)) {
|
|
183
|
-
Logger.error(`${fileType}表 ${fileName} 文件 ${fieldName} 索引标识 "${isIndexStr}" 格式错误,必须为0或1`);
|
|
184
|
-
fileValid = false;
|
|
185
|
-
continue;
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
// 第7个值:必须为null或正则表达式
|
|
189
|
-
if (!validateRegex(regexConstraint)) {
|
|
190
|
-
Logger.error(`${fileType}表 ${fileName} 文件 ${fieldName} 正则约束 "${regexConstraint}" 格式错误,必须为null或有效的正则表达式`);
|
|
191
|
-
fileValid = false;
|
|
192
|
-
continue;
|
|
193
|
-
}
|
|
194
|
-
} catch (error) {
|
|
195
|
-
Logger.error(`${fileType}表 ${fileName} 文件 ${fieldName} 验证规则解析失败: ${error.message}`);
|
|
196
|
-
fileValid = false;
|
|
197
|
-
continue;
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
if (fileValid) {
|
|
202
|
-
validFiles++;
|
|
203
|
-
} else {
|
|
204
|
-
invalidFiles++;
|
|
205
|
-
}
|
|
206
|
-
} catch (error) {
|
|
207
|
-
Logger.error(`${fileType}表 ${fileName} 解析失败: ${error.message}`);
|
|
208
|
-
invalidFiles++;
|
|
209
|
-
}
|
|
210
|
-
}
|
|
211
|
-
|
|
212
|
-
if (invalidFiles > 0) {
|
|
213
|
-
return false;
|
|
214
|
-
} else {
|
|
215
|
-
return true;
|
|
216
|
-
}
|
|
217
|
-
} catch (error) {
|
|
218
|
-
Logger.error(`Tables 检查过程中出错:`, error);
|
|
219
|
-
return false;
|
|
220
|
-
}
|
|
221
|
-
};
|