befly 2.0.12 → 2.1.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/.gitignore +94 -0
- package/.npmrc +3 -0
- package/.prettierignore +2 -0
- package/.prettierrc +11 -0
- package/README.md +2 -2
- package/checks/table.js +215 -0
- package/config/env.js +1 -0
- package/main.js +43 -60
- package/package.json +25 -5
- package/plugins/db.js +137 -178
- package/scripts/syncDb.js +367 -0
- package/system.js +118 -0
- package/tables/common.json +16 -0
- package/tables/tool.json +6 -0
- package/utils/util.js +123 -0
- package/utils/validate.js +115 -99
- package/checks/schema.js +0 -132
- package/schema/common.json +0 -17
- package/schema/debug.json +0 -1
- package/schema/health.json +0 -1
- package/schema/tool.json +0 -6
package/utils/validate.js
CHANGED
|
@@ -1,4 +1,96 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { isType } from './util.js';
|
|
2
|
+
|
|
3
|
+
// 验证字段名称是否为中文、数字、字母
|
|
4
|
+
const validateFieldName = (name) => {
|
|
5
|
+
const nameRegex = /^[\u4e00-\u9fa5a-zA-Z0-9]+$/;
|
|
6
|
+
return nameRegex.test(name);
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
// 验证字段类型是否为指定的四种类型之一
|
|
10
|
+
const validateFieldType = (type) => {
|
|
11
|
+
const validTypes = ['string', 'number', 'text', 'array'];
|
|
12
|
+
return validTypes.includes(type);
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
// 验证最小值/最大值是否为null或数字
|
|
16
|
+
const validateMinMax = (value) => {
|
|
17
|
+
return value === 'null' || (!isNaN(parseFloat(value)) && isFinite(parseFloat(value)));
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
// 验证默认值是否为null、字符串或数字
|
|
21
|
+
const validateDefaultValue = (value) => {
|
|
22
|
+
if (value === 'null') return true;
|
|
23
|
+
// 检查是否为数字
|
|
24
|
+
if (!isNaN(parseFloat(value)) && isFinite(parseFloat(value))) return true;
|
|
25
|
+
// 其他情况视为字符串,都是有效的
|
|
26
|
+
return true;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// 验证索引标识是否为0或1
|
|
30
|
+
const validateIndex = (value) => {
|
|
31
|
+
return value === '0' || value === '1';
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
// 验证正则表达式是否有效
|
|
35
|
+
const validateRegex = (value) => {
|
|
36
|
+
if (value === 'null') return true;
|
|
37
|
+
try {
|
|
38
|
+
new RegExp(value);
|
|
39
|
+
return true;
|
|
40
|
+
} catch (e) {
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
// 专门用于处理⚡分隔的字段规则
|
|
46
|
+
const parseFieldRule = (rule) => {
|
|
47
|
+
const allParts = rule.split('⚡');
|
|
48
|
+
|
|
49
|
+
// 必须包含7个部分:显示名⚡类型⚡最小值⚡最大值⚡默认值⚡是否索引⚡正则约束
|
|
50
|
+
if (allParts.length !== 7) {
|
|
51
|
+
throw new Error(`字段规则格式错误,必须包含7个部分,当前包含${allParts.length}个部分`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// 验证各个部分的格式
|
|
55
|
+
const [name, type, minValue, maxValue, defaultValue, isIndex, regexConstraint] = allParts;
|
|
56
|
+
|
|
57
|
+
// 第1个值:名称必须为中文、数字、字母
|
|
58
|
+
if (!validateFieldName(name)) {
|
|
59
|
+
throw new Error(`字段名称 "${name}" 格式错误,必须为中文、数字、字母`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// 第2个值:字段类型必须为string,number,text,array之一
|
|
63
|
+
if (!validateFieldType(type)) {
|
|
64
|
+
throw new Error(`字段类型 "${type}" 格式错误,必须为string、number、text、array之一`);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// 第3个值:最小值必须为null或数字
|
|
68
|
+
if (!validateMinMax(minValue)) {
|
|
69
|
+
throw new Error(`最小值 "${minValue}" 格式错误,必须为null或数字`);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// 第4个值:最大值必须为null或数字
|
|
73
|
+
if (!validateMinMax(maxValue)) {
|
|
74
|
+
throw new Error(`最大值 "${maxValue}" 格式错误,必须为null或数字`);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// 第5个值:默认值必须为null、字符串或数字
|
|
78
|
+
if (!validateDefaultValue(defaultValue)) {
|
|
79
|
+
throw new Error(`默认值 "${defaultValue}" 格式错误,必须为null、字符串或数字`);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
// 第6个值:是否创建索引必须为0或1
|
|
83
|
+
if (!validateIndex(isIndex)) {
|
|
84
|
+
throw new Error(`索引标识 "${isIndex}" 格式错误,必须为0或1`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// 第7个值:必须为null或正则表达式
|
|
88
|
+
if (!validateRegex(regexConstraint)) {
|
|
89
|
+
throw new Error(`正则约束 "${regexConstraint}" 格式错误,必须为null或有效的正则表达式`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return allParts;
|
|
93
|
+
};
|
|
2
94
|
|
|
3
95
|
/**
|
|
4
96
|
* 验证器类
|
|
@@ -63,7 +155,7 @@ export class Validator {
|
|
|
63
155
|
for (const fieldName of required) {
|
|
64
156
|
if (!(fieldName in data) || data[fieldName] === undefined || data[fieldName] === null || data[fieldName] === '') {
|
|
65
157
|
result.code = 1;
|
|
66
|
-
const ruleParts = rules[fieldName]
|
|
158
|
+
const ruleParts = parseFieldRule(rules[fieldName] || '');
|
|
67
159
|
const fieldLabel = ruleParts[0] || fieldName;
|
|
68
160
|
result.fields[fieldName] = `${fieldLabel}(${fieldName})为必填项`;
|
|
69
161
|
}
|
|
@@ -99,16 +191,18 @@ export class Validator {
|
|
|
99
191
|
* 验证单个字段的值
|
|
100
192
|
*/
|
|
101
193
|
validateFieldValue(value, rule, fieldName) {
|
|
102
|
-
const [name, type, minStr, maxStr,
|
|
194
|
+
const [name, type, minStr, maxStr, defaultValue, isIndexStr, regexConstraint] = parseFieldRule(rule);
|
|
103
195
|
const min = minStr === 'null' ? null : parseInt(minStr) || 0;
|
|
104
196
|
const max = maxStr === 'null' ? null : parseInt(maxStr) || 0;
|
|
105
|
-
const spec =
|
|
197
|
+
const spec = regexConstraint === 'null' ? null : regexConstraint.trim();
|
|
106
198
|
|
|
107
199
|
switch (type.toLowerCase()) {
|
|
108
200
|
case 'number':
|
|
109
201
|
return this.validateNumber(value, name, min, max, spec, fieldName);
|
|
110
202
|
case 'string':
|
|
111
203
|
return this.validateString(value, name, min, max, spec, fieldName);
|
|
204
|
+
case 'text':
|
|
205
|
+
return this.validateString(value, name, min, max, spec, fieldName);
|
|
112
206
|
case 'array':
|
|
113
207
|
return this.validateArray(value, name, min, max, spec, fieldName);
|
|
114
208
|
default:
|
|
@@ -129,101 +223,27 @@ export class Validator {
|
|
|
129
223
|
return `${name}(${fieldName})不能小于${min}`;
|
|
130
224
|
}
|
|
131
225
|
|
|
132
|
-
if (
|
|
226
|
+
if (max !== null && max > 0 && value > max) {
|
|
133
227
|
return `${name}(${fieldName})不能大于${max}`;
|
|
134
228
|
}
|
|
135
229
|
|
|
136
230
|
if (spec && spec.trim() !== '') {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
231
|
+
try {
|
|
232
|
+
const regExp = new RegExp(spec);
|
|
233
|
+
if (!regExp.test(String(value))) {
|
|
234
|
+
return `${name}(${fieldName})格式不正确`;
|
|
235
|
+
}
|
|
236
|
+
} catch (error) {
|
|
237
|
+
return `${name}(${fieldName})的正则表达式格式错误`;
|
|
143
238
|
}
|
|
144
239
|
}
|
|
145
240
|
|
|
146
241
|
return null;
|
|
147
242
|
} catch (error) {
|
|
148
|
-
return `${name}(${fieldName})
|
|
243
|
+
return `${name}(${fieldName})验证出错: ${error.message}`;
|
|
149
244
|
}
|
|
150
245
|
}
|
|
151
246
|
|
|
152
|
-
/**
|
|
153
|
-
* 判断是否为枚举格式
|
|
154
|
-
*/
|
|
155
|
-
isEnumSpec(spec) {
|
|
156
|
-
return spec && spec.startsWith('enum#');
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
/**
|
|
160
|
-
* 验证枚举值
|
|
161
|
-
*/
|
|
162
|
-
validateEnum(value, spec, name, fieldName, type) {
|
|
163
|
-
// 解析枚举值 "enum#1,2,3" -> ["1", "2", "3"]
|
|
164
|
-
const enumValues = spec
|
|
165
|
-
.substring(5)
|
|
166
|
-
.split(',')
|
|
167
|
-
.map((v) => v.trim());
|
|
168
|
-
|
|
169
|
-
if (type === 'number') {
|
|
170
|
-
// 数字类型:转换枚举值为数字进行比较
|
|
171
|
-
const numericEnumValues = enumValues.map((v) => parseFloat(v));
|
|
172
|
-
if (!numericEnumValues.includes(value)) {
|
|
173
|
-
return `${name}(${fieldName})必须是以下值之一: ${enumValues.join(', ')}`;
|
|
174
|
-
}
|
|
175
|
-
} else if (type === 'string') {
|
|
176
|
-
// 字符串类型:直接比较
|
|
177
|
-
if (!enumValues.includes(value)) {
|
|
178
|
-
return `${name}(${fieldName})必须是以下值之一: ${enumValues.join(', ')}`;
|
|
179
|
-
}
|
|
180
|
-
} else if (type === 'array') {
|
|
181
|
-
// 数组类型:检查每个元素是否在枚举值中
|
|
182
|
-
for (const item of value) {
|
|
183
|
-
if (!enumValues.includes(String(item))) {
|
|
184
|
-
return `${name}(${fieldName})中的元素"${item}"必须是以下值之一: ${enumValues.join(', ')}`;
|
|
185
|
-
}
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
return null;
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
/**
|
|
193
|
-
* 验证数字表达式
|
|
194
|
-
*/
|
|
195
|
-
validateNumberExpression(value, name, spec, fieldName) {
|
|
196
|
-
const parts = spec.split('=');
|
|
197
|
-
if (parts.length !== 2) {
|
|
198
|
-
return `${name}(${fieldName})的计算规则必须包含等号`;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
const leftExpression = parts[0].trim();
|
|
202
|
-
const rightValue = parseFloat(parts[1].trim());
|
|
203
|
-
|
|
204
|
-
if (isNaN(rightValue)) {
|
|
205
|
-
return `${name}(${fieldName})的计算规则右边必须是数字`;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
const safePattern = /^[x\d\+\-\*\/\(\)\.\s]+$/;
|
|
209
|
-
if (!safePattern.test(leftExpression)) {
|
|
210
|
-
return `${name}(${fieldName})的表达式包含不安全的字符`;
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
let processedExpression = leftExpression.replace(/x/g, value.toString());
|
|
214
|
-
const leftResult = new Function('return ' + processedExpression)();
|
|
215
|
-
|
|
216
|
-
if (typeof leftResult !== 'number' || !isFinite(leftResult)) {
|
|
217
|
-
return `${name}(${fieldName})的表达式计算结果不是有效数字`;
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
if (Math.abs(leftResult - rightValue) > Number.EPSILON) {
|
|
221
|
-
return `${name}(${fieldName})不满足计算条件 ${spec}`;
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
return null;
|
|
225
|
-
}
|
|
226
|
-
|
|
227
247
|
/**
|
|
228
248
|
* 验证字符串类型
|
|
229
249
|
*/
|
|
@@ -242,21 +262,19 @@ export class Validator {
|
|
|
242
262
|
}
|
|
243
263
|
|
|
244
264
|
if (spec && spec.trim() !== '') {
|
|
245
|
-
|
|
246
|
-
if (this.isEnumSpec(spec)) {
|
|
247
|
-
return this.validateEnum(value, spec, name, fieldName, 'string');
|
|
248
|
-
} else {
|
|
249
|
-
// 原有的正则表达式验证逻辑
|
|
265
|
+
try {
|
|
250
266
|
const regExp = new RegExp(spec);
|
|
251
267
|
if (!regExp.test(value)) {
|
|
252
268
|
return `${name}(${fieldName})格式不正确`;
|
|
253
269
|
}
|
|
270
|
+
} catch (error) {
|
|
271
|
+
return `${name}(${fieldName})的正则表达式格式错误`;
|
|
254
272
|
}
|
|
255
273
|
}
|
|
256
274
|
|
|
257
275
|
return null;
|
|
258
276
|
} catch (error) {
|
|
259
|
-
return `${name}(${fieldName})
|
|
277
|
+
return `${name}(${fieldName})验证出错: ${error.message}`;
|
|
260
278
|
}
|
|
261
279
|
}
|
|
262
280
|
|
|
@@ -278,23 +296,21 @@ export class Validator {
|
|
|
278
296
|
}
|
|
279
297
|
|
|
280
298
|
if (spec && spec.trim() !== '') {
|
|
281
|
-
|
|
282
|
-
if (this.isEnumSpec(spec)) {
|
|
283
|
-
return this.validateEnum(value, spec, name, fieldName, 'array');
|
|
284
|
-
} else {
|
|
285
|
-
// 原有的正则表达式验证逻辑
|
|
299
|
+
try {
|
|
286
300
|
const regExp = new RegExp(spec);
|
|
287
301
|
for (const item of value) {
|
|
288
302
|
if (!regExp.test(String(item))) {
|
|
289
303
|
return `${name}(${fieldName})中的元素"${item}"格式不正确`;
|
|
290
304
|
}
|
|
291
305
|
}
|
|
306
|
+
} catch (error) {
|
|
307
|
+
return `${name}(${fieldName})的正则表达式格式错误`;
|
|
292
308
|
}
|
|
293
309
|
}
|
|
294
310
|
|
|
295
311
|
return null;
|
|
296
312
|
} catch (error) {
|
|
297
|
-
return `${name}(${fieldName})
|
|
313
|
+
return `${name}(${fieldName})验证出错: ${error.message}`;
|
|
298
314
|
}
|
|
299
315
|
}
|
|
300
316
|
}
|
package/checks/schema.js
DELETED
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
import path from 'node:path';
|
|
2
|
-
import { ruleSplit } from '../utils/util.js';
|
|
3
|
-
import { Logger } from '../utils/Logger.js';
|
|
4
|
-
|
|
5
|
-
export default async () => {
|
|
6
|
-
try {
|
|
7
|
-
const schemaGlob = new Bun.Glob('*.json');
|
|
8
|
-
const coreSchemaDir = path.join(__dirname, '..', 'schema');
|
|
9
|
-
const userSchemaDir = path.join(process.cwd(), 'schema');
|
|
10
|
-
|
|
11
|
-
// 统计信息
|
|
12
|
-
let totalFiles = 0;
|
|
13
|
-
let totalRules = 0;
|
|
14
|
-
let validFiles = 0;
|
|
15
|
-
let invalidFiles = 0;
|
|
16
|
-
|
|
17
|
-
const validateFile = async (file) => {
|
|
18
|
-
totalFiles++;
|
|
19
|
-
const fileName = path.basename(file);
|
|
20
|
-
try {
|
|
21
|
-
// 读取并解析 JSON 文件
|
|
22
|
-
const schema = await Bun.file(file).json();
|
|
23
|
-
let fileValid = true;
|
|
24
|
-
let fileRules = 0;
|
|
25
|
-
|
|
26
|
-
// 检查 schema 中的每个验证规则
|
|
27
|
-
for (const [fieldName, rule] of Object.entries(schema)) {
|
|
28
|
-
fileRules++;
|
|
29
|
-
totalRules++;
|
|
30
|
-
|
|
31
|
-
// 验证规则格式
|
|
32
|
-
const ruleParts = ruleSplit(rule);
|
|
33
|
-
|
|
34
|
-
if (ruleParts.length !== 5) {
|
|
35
|
-
Logger.warn(`${fileName} 文件 ${fieldName} 验证规则错误,应包含 5 个部分,但包含 ${ruleParts.length} 个部分`);
|
|
36
|
-
fileValid = false;
|
|
37
|
-
continue;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
const [name, type, minStr, maxStr, spec] = ruleParts;
|
|
41
|
-
|
|
42
|
-
// 验证类型(必须严格使用小写类型名称)
|
|
43
|
-
const validTypes = ['number', 'string', 'array'];
|
|
44
|
-
if (!validTypes.includes(type)) {
|
|
45
|
-
Logger.warn(`${fileName} 文件 ${fieldName} 类型 ${type} 不支持,应为小写的 number、string 或 array`);
|
|
46
|
-
fileValid = false;
|
|
47
|
-
continue;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
// 验证最小值/最大值
|
|
51
|
-
if (minStr !== 'null' && isNaN(parseInt(minStr))) {
|
|
52
|
-
Logger.warn(`${fileName} 文件 ${fieldName} 最小值 ${minStr} 应为数字或 "null"`);
|
|
53
|
-
fileValid = false;
|
|
54
|
-
continue;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
if (maxStr !== 'null' && isNaN(parseInt(maxStr))) {
|
|
58
|
-
Logger.warn(`${fileName} 文件 ${fieldName} 最大值 ${maxStr} 应为数字或 "null"`);
|
|
59
|
-
fileValid = false;
|
|
60
|
-
continue;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// 验证特殊规则
|
|
64
|
-
if (spec !== 'null') {
|
|
65
|
-
if (type === 'number' && spec.includes('=')) {
|
|
66
|
-
// 数字计算表达式应包含安全字符
|
|
67
|
-
const safePattern = /^[x\d\+\-\*\/\(\)\.\s\%]+$/;
|
|
68
|
-
const expressionPart = spec.split('=')[0].trim();
|
|
69
|
-
|
|
70
|
-
if (!safePattern.test(expressionPart)) {
|
|
71
|
-
Logger.warn(`${fileName} 文件 ${fieldName} 表达式 ${expressionPart} 包含不安全的字符`);
|
|
72
|
-
fileValid = false;
|
|
73
|
-
continue;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
// 验证等号右侧是否为数字
|
|
77
|
-
const rightPart = spec.split('=')[1].trim();
|
|
78
|
-
if (isNaN(parseFloat(rightPart))) {
|
|
79
|
-
Logger.error(`${fileName} 文件 ${fieldName} 计算规则右边必须是数字,而不是 ${rightPart}`);
|
|
80
|
-
fileValid = false;
|
|
81
|
-
continue;
|
|
82
|
-
}
|
|
83
|
-
} else if (type === 'string' || type === 'array') {
|
|
84
|
-
// 尝试编译正则表达式以检查是否有效
|
|
85
|
-
try {
|
|
86
|
-
new RegExp(spec);
|
|
87
|
-
} catch (e) {
|
|
88
|
-
Logger.error(`${fileName} 文件 ${fieldName} 正则表达式 ${spec} 无效: ${e.message}`);
|
|
89
|
-
fileValid = false;
|
|
90
|
-
continue;
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if (fileValid) {
|
|
97
|
-
validFiles++;
|
|
98
|
-
} else {
|
|
99
|
-
invalidFiles++;
|
|
100
|
-
}
|
|
101
|
-
} catch (error) {
|
|
102
|
-
Logger.error(`Schema ${fileName} 解析失败: ${error.message}`);
|
|
103
|
-
invalidFiles++;
|
|
104
|
-
}
|
|
105
|
-
};
|
|
106
|
-
|
|
107
|
-
for await (const file of schemaGlob.scan({
|
|
108
|
-
cwd: coreSchemaDir,
|
|
109
|
-
absolute: true,
|
|
110
|
-
onlyFiles: true
|
|
111
|
-
})) {
|
|
112
|
-
await validateFile(file);
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
for await (const file of schemaGlob.scan({
|
|
116
|
-
cwd: userSchemaDir,
|
|
117
|
-
absolute: true,
|
|
118
|
-
onlyFiles: true
|
|
119
|
-
})) {
|
|
120
|
-
await validateFile(file);
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
if (invalidFiles > 0) {
|
|
124
|
-
return false;
|
|
125
|
-
} else {
|
|
126
|
-
return true;
|
|
127
|
-
}
|
|
128
|
-
} catch (error) {
|
|
129
|
-
Logger.error(`Schema 检查过程中出错:`, error);
|
|
130
|
-
return false;
|
|
131
|
-
}
|
|
132
|
-
};
|
package/schema/common.json
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"id": "ID,number,1,999999999,x%2=0",
|
|
3
|
-
"email": "邮箱,string,5,100,^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\\.[a-zA-Z]{2,}$",
|
|
4
|
-
"phone": "手机号,string,11,11,^1[3-9]\\d{9}$",
|
|
5
|
-
"page": "页码,number,1,9999,null",
|
|
6
|
-
"limit": "每页数量,number,1,100,null",
|
|
7
|
-
"title": "标题,string,1,200,null",
|
|
8
|
-
"description": "描述,string,0,500,null",
|
|
9
|
-
"keyword": "关键词,string,1,50,null",
|
|
10
|
-
"status": "状态,string,1,20,null",
|
|
11
|
-
"enabled": "启用状态,number,0,1,null",
|
|
12
|
-
"date": "日期,string,10,10,^\\d{4}-\\d{2}-\\d{2}$",
|
|
13
|
-
"datetime": "日期时间,string,19,25,^\\d{4}-\\d{2}-\\d{2}T\\d{2}:\\d{2}:\\d{2}",
|
|
14
|
-
"filename": "文件名,string,1,255,null",
|
|
15
|
-
"url": "网址,string,5,500,^https?://",
|
|
16
|
-
"tag": "标签,array,0,10,null"
|
|
17
|
-
}
|
package/schema/debug.json
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{}
|
package/schema/health.json
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{}
|