befly 3.76.1 → 3.76.3
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/Befly.js +159 -0
- package/README.md +50 -11
- package/apis/admin/delete.js +1 -3
- package/apis/admin/select.js +2 -7
- package/apis/admin/update.js +1 -5
- package/apis/api/select.js +2 -7
- package/apis/auth/login.js +16 -20
- package/apis/dict/delete.js +1 -1
- package/apis/dict/detail.js +1 -3
- package/apis/dict/select.js +1 -2
- package/apis/dict/update.js +1 -1
- package/apis/dictType/delete.js +1 -1
- package/apis/dictType/detail.js +1 -1
- package/apis/dictType/insert.js +1 -1
- package/apis/dictType/select.js +2 -7
- package/apis/dictType/update.js +2 -2
- package/apis/email/config.js +4 -7
- package/apis/email/logList.js +2 -7
- package/apis/email/send.js +1 -2
- package/apis/email/verify.js +1 -3
- package/apis/loginLog/select.js +2 -7
- package/apis/menu/select.js +2 -7
- package/apis/operateLog/select.js +2 -7
- package/apis/role/apiSave.js +2 -2
- package/apis/role/menuSave.js +2 -2
- package/apis/role/select.js +2 -7
- package/apis/source/imageList.js +2 -5
- package/apis/tongJi/dailyReport.js +12 -13
- package/apis/tongJi/dailyReportSelect.js +1 -2
- package/apis/tongJi/dailyStats.js +1 -1
- package/apis/tongJi/dailyStatsDistribution.js +2 -2
- package/apis/tongJi/errorList.js +1 -2
- package/apis/tongJi/errorReport.js +1 -2
- package/apis/tongJi/projectDelete.js +1 -3
- package/apis/tongJi/projectSelect.js +2 -4
- package/apis/tongJi/projectUpdate.js +2 -2
- package/apis/upload/file.js +7 -5
- package/checks/api.js +43 -66
- package/checks/config.js +17 -12
- package/checks/corns.js +28 -46
- package/checks/field.js +18 -0
- package/checks/items.js +16 -60
- package/checks/menu.js +28 -39
- package/checks/table.js +106 -26
- package/checks/validation.js +13 -0
- package/configs/beflyConfig.json +6 -6
- package/configs/constConfig.js +2 -63
- package/exports.js +2 -0
- package/hooks/auth.js +11 -7
- package/hooks/parser.js +55 -59
- package/hooks/permission.js +1 -0
- package/hooks/rateLimit.js +25 -62
- package/hooks/validator.js +43 -31
- package/index.js +43 -344
- package/{lib → libs}/cacheHelper.js +3 -8
- package/libs/cron.js +60 -0
- package/libs/logger/index.js +1 -0
- package/libs/logger/logger.js +245 -0
- package/libs/logger/sanitize.js +115 -0
- package/{lib → libs/mysql}/dbHelper.js +100 -73
- package/{lib → libs/mysql}/dbParse.js +16 -9
- package/{lib → libs/mysql}/dbUtil.js +15 -32
- package/libs/mysql/error.js +29 -0
- package/libs/mysql/index.js +6 -0
- package/libs/mysql/is.js +29 -0
- package/libs/mysql/mysql.js +43 -0
- package/libs/mysql/sql/identifier.js +85 -0
- package/libs/mysql/sql/index.js +2 -0
- package/{lib → libs/mysql/sql}/sqlBuilder.js +2 -3
- package/libs/mysql/util.js +44 -0
- package/libs/redis/index.js +2 -0
- package/libs/redis/redis.js +204 -0
- package/libs/redis/redisError.js +8 -0
- package/libs/tableModel.js +72 -0
- package/libs/tool.js +33 -0
- package/libs/validator/compiler.js +248 -0
- package/libs/validator/index.js +10 -0
- package/libs/validator/parser.js +173 -0
- package/libs/validator/util.js +5 -0
- package/package.json +10 -8
- package/paths.js +2 -3
- package/plugins/cache.js +2 -1
- package/plugins/email.js +4 -4
- package/plugins/mysql.js +5 -15
- package/plugins/redis.js +4 -12
- package/router/api.js +14 -10
- package/router/root.js +14 -0
- package/router/static.js +3 -3
- package/schemas/api.json +99 -0
- package/schemas/config.json +255 -0
- package/schemas/corn.json +39 -0
- package/schemas/item.json +43 -0
- package/schemas/menu.json +44 -0
- package/schemas/table.json +80 -0
- package/scripts/syncDb/apply.js +87 -0
- package/scripts/syncDb/ddl.js +43 -0
- package/scripts/syncDb/index.js +175 -30
- package/scripts/syncDb/model.js +60 -0
- package/scripts/syncDb/plan.js +160 -0
- package/scripts/syncDb/preflight.js +98 -0
- package/scripts/syncDb/report.js +177 -162
- package/scripts/syncDb/snapshot.js +118 -0
- package/tables/admin.json +51 -53
- package/tables/api.json +28 -39
- package/tables/dailyReport.json +85 -73
- package/tables/dict.json +27 -39
- package/tables/dictType.json +20 -34
- package/tables/emailLog.json +49 -47
- package/tables/errorReport.json +86 -60
- package/tables/file.json +47 -50
- package/tables/loginLog.json +68 -55
- package/tables/menu.json +20 -34
- package/tables/operateLog.json +53 -47
- package/tables/project.json +18 -30
- package/tables/role.json +33 -37
- package/utils/cors.js +37 -41
- package/utils/deepMerge.js +3 -1
- package/utils/error.js +1 -56
- package/utils/id.js +44 -0
- package/utils/is.js +6 -243
- package/utils/response.js +12 -1
- package/utils/scanFiles.js +13 -4
- package/utils/scanSources.js +58 -20
- package/utils/util.js +11 -163
- package/apis/_apis.js +0 -20
- package/apis/auth/sendSmsCode.js +0 -24
- package/configs/regexpAlias.json +0 -55
- package/lib/connect.js +0 -119
- package/lib/logger.js +0 -430
- package/lib/redisHelper.js +0 -399
- package/lib/validator.js +0 -274
- package/lib/xmlParse.js +0 -149
- package/plugins/config.js +0 -13
- package/plugins/cron.js +0 -115
- package/plugins/logger.js +0 -17
- package/plugins/tool.js +0 -131
- package/plugins/xmlParse.js +0 -10
- package/schemas/config.js +0 -67
- package/scripts/syncDb/context.js +0 -120
- package/scripts/syncDb/diff.js +0 -279
- package/scripts/syncDb/transform.js +0 -113
- package/sql/befly.sql +0 -271
- package/utils/email.js +0 -3
- package/utils/fieldClear.js +0 -22
- package/utils/formatValidationIssues.js +0 -28
- package/utils/getClientIp.js +0 -47
- package/utils/loggerUtils.js +0 -196
- package/utils/regexpUtil.js +0 -52
- /package/{lib → libs}/smtpText.js +0 -0
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
import { parseNode } from "./parser.js";
|
|
2
|
+
import { isPlainObject } from "./util.js";
|
|
3
|
+
|
|
4
|
+
const TYPE_KEYS = {
|
|
5
|
+
any: new Set(["paramType", "optional", "paramDefault"]),
|
|
6
|
+
boolean: new Set(["paramType", "optional", "paramDefault", "enum"]),
|
|
7
|
+
integer: new Set(["paramType", "optional", "paramDefault", "enum", "minValue", "maxValue"]),
|
|
8
|
+
number: new Set(["paramType", "optional", "paramDefault", "enum", "minValue", "maxValue", "precision", "scale"]),
|
|
9
|
+
string: new Set(["paramType", "optional", "paramDefault", "enum", "minValue", "maxValue", "pattern"]),
|
|
10
|
+
function: new Set(["paramType", "optional"]),
|
|
11
|
+
array: new Set(["paramType", "optional", "paramDefault", "minItem", "maxItem", "unique", "uniqueBy", "items"]),
|
|
12
|
+
object: new Set(["paramType", "optional", "paramDefault", "minItem", "maxItem", "fields", "extra", "constraints"]),
|
|
13
|
+
record: new Set(["paramType", "optional", "paramDefault", "minItem", "maxItem", "keys", "values"])
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const SCHEMA_TYPES = new Set(Object.keys(TYPE_KEYS));
|
|
17
|
+
const ANY_OF_KEYS = new Set(["anyOf", "optional", "paramDefault"]);
|
|
18
|
+
const COMPARE_OPERATORS = new Set(["eq", "neq", "gt", "gte", "lt", "lte"]);
|
|
19
|
+
const PRESENCE_MODES = new Set(["together", "atLeastOne", "exactlyOne"]);
|
|
20
|
+
|
|
21
|
+
function schemaError(path, message) {
|
|
22
|
+
throw new TypeError(`Schema ${path.length > 0 ? path.join(".") : "(root)"} ${message}`);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function assertBoolean(value, path) {
|
|
26
|
+
if (value !== undefined && typeof value !== "boolean") schemaError(path, "必须是 boolean");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function assertLimit(value, path) {
|
|
30
|
+
if (value !== undefined && (!Number.isSafeInteger(value) || value < 0)) schemaError(path, "必须是大于等于 0 的安全整数");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function assertNumber(value, path) {
|
|
34
|
+
if (value !== undefined && (typeof value !== "number" || !Number.isFinite(value))) schemaError(path, "必须是有限数字");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function assertValueBounds(schema, path) {
|
|
38
|
+
assertNumber(schema.minValue, [...path, "minValue"]);
|
|
39
|
+
assertNumber(schema.maxValue, [...path, "maxValue"]);
|
|
40
|
+
if (schema.minValue !== undefined && schema.maxValue !== undefined && schema.minValue > schema.maxValue) schemaError(path, "minValue 不能大于 maxValue");
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function assertStringBounds(schema, path) {
|
|
44
|
+
assertLimit(schema.minValue, [...path, "minValue"]);
|
|
45
|
+
assertLimit(schema.maxValue, [...path, "maxValue"]);
|
|
46
|
+
if (schema.minValue !== undefined && schema.maxValue !== undefined && schema.minValue > schema.maxValue) schemaError(path, "minValue 不能大于 maxValue");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function assertItemBounds(schema, path) {
|
|
50
|
+
assertLimit(schema.minItem, [...path, "minItem"]);
|
|
51
|
+
assertLimit(schema.maxItem, [...path, "maxItem"]);
|
|
52
|
+
if (schema.minItem !== undefined && schema.maxItem !== undefined && schema.minItem > schema.maxItem) schemaError(path, "minItem 不能大于 maxItem");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function isValueType(value, type) {
|
|
56
|
+
if (type === "boolean") return typeof value === "boolean";
|
|
57
|
+
if (type === "integer") return Number.isSafeInteger(value);
|
|
58
|
+
if (type === "number") return typeof value === "number" && Number.isFinite(value);
|
|
59
|
+
return typeof value === "string" && value === value.trim();
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function compileEnum(schema, path) {
|
|
63
|
+
if (schema.enum === undefined) return null;
|
|
64
|
+
if (!Array.isArray(schema.enum) || schema.enum.length === 0) schemaError([...path, "enum"], "必须是非空数组");
|
|
65
|
+
if (schema.enum.some((value) => !isValueType(value, schema.paramType))) schemaError([...path, "enum"], `所有值必须符合 ${schema.paramType}`);
|
|
66
|
+
const values = new Set(schema.enum);
|
|
67
|
+
if (values.size !== schema.enum.length) schemaError([...path, "enum"], "不能包含重复值");
|
|
68
|
+
return values;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function assertObjectKeys(value, allowedKeys, path) {
|
|
72
|
+
if (!isPlainObject(value)) schemaError(path, "必须是对象");
|
|
73
|
+
for (const key of Object.keys(value)) if (!allowedKeys.has(key)) schemaError([...path, key], "是未知配置");
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
function assertFieldNames(fields, names, path) {
|
|
77
|
+
if (!Array.isArray(names) || names.length === 0) schemaError(path, "必须是非空数组");
|
|
78
|
+
const uniqueNames = new Set(names);
|
|
79
|
+
if (uniqueNames.size !== names.length) schemaError(path, "不能包含重复字段");
|
|
80
|
+
for (const name of names) if (typeof name !== "string" || !fields.has(name)) schemaError(path, `引用了不存在的字段 ${name}`);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function compileConstraints(schema, node, path) {
|
|
84
|
+
if (schema.constraints === undefined) return [];
|
|
85
|
+
if (!Array.isArray(schema.constraints) || schema.constraints.length === 0) schemaError([...path, "constraints"], "必须是非空数组");
|
|
86
|
+
return schema.constraints.map((constraint, index) => {
|
|
87
|
+
const constraintPath = [...path, "constraints", index];
|
|
88
|
+
assertObjectKeys(constraint, new Set(["compare", "when", "presence", "message"]), constraintPath);
|
|
89
|
+
if (constraint.message !== undefined && (typeof constraint.message !== "string" || !constraint.message.trim() || constraint.message !== constraint.message.trim())) schemaError([...constraintPath, "message"], "必须是非空无首尾空白字符串");
|
|
90
|
+
const kinds = ["compare", "when", "presence"].filter((key) => constraint[key] !== undefined);
|
|
91
|
+
if (kinds.length !== 1) schemaError(constraintPath, "必须且只能配置 compare、when、presence 中的一种");
|
|
92
|
+
const kind = kinds[0];
|
|
93
|
+
if (kind === "compare") return compileCompare(constraint.compare, constraint.message, node, [...constraintPath, "compare"]);
|
|
94
|
+
if (kind === "when") return compileWhen(constraint.when, constraint.message, node, [...constraintPath, "when"]);
|
|
95
|
+
return compilePresence(constraint.presence, constraint.message, node, [...constraintPath, "presence"]);
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function compileCompare(compare, message, node, path) {
|
|
100
|
+
assertObjectKeys(compare, new Set(["left", "operator", "right"]), path);
|
|
101
|
+
const left = node.fieldMap.get(compare.left);
|
|
102
|
+
const right = node.fieldMap.get(compare.right);
|
|
103
|
+
if (!left) schemaError([...path, "left"], "引用字段不存在");
|
|
104
|
+
if (!right) schemaError([...path, "right"], "引用字段不存在");
|
|
105
|
+
if (compare.left === compare.right) schemaError(path, "左右字段不能相同");
|
|
106
|
+
if (!COMPARE_OPERATORS.has(compare.operator)) schemaError([...path, "operator"], "无效");
|
|
107
|
+
if (["eq", "neq"].includes(compare.operator)) {
|
|
108
|
+
if (!["boolean", "integer", "number", "string"].includes(left.kind) || left.kind !== right.kind) schemaError(path, "eq/neq 两侧必须是相同标量类型");
|
|
109
|
+
} else if (!["integer", "number"].includes(left.kind) || !["integer", "number"].includes(right.kind)) schemaError(path, "大小比较两侧必须是 integer 或 number");
|
|
110
|
+
return { kind: "compare", left: compare.left, operator: compare.operator, right: compare.right, message: message };
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
function compileWhen(when, message, node, path) {
|
|
114
|
+
assertObjectKeys(when, new Set(["field", "is", "required", "forbidden"]), path);
|
|
115
|
+
const field = node.fieldMap.get(when.field);
|
|
116
|
+
if (!field) schemaError([...path, "field"], "引用字段不存在");
|
|
117
|
+
if (!["boolean", "integer", "number", "string"].includes(field.kind) || !isValueType(when.is, field.kind)) schemaError([...path, "is"], "必须符合条件字段类型");
|
|
118
|
+
if (field.enumSet && !field.enumSet.has(when.is)) schemaError([...path, "is"], "必须属于条件字段 enum");
|
|
119
|
+
if (when.required === undefined && when.forbidden === undefined) schemaError(path, "required 和 forbidden 至少配置一项");
|
|
120
|
+
if (when.required !== undefined) assertFieldNames(node.fieldSet, when.required, [...path, "required"]);
|
|
121
|
+
if (when.forbidden !== undefined) assertFieldNames(node.fieldSet, when.forbidden, [...path, "forbidden"]);
|
|
122
|
+
const required = when.required || [];
|
|
123
|
+
const forbidden = when.forbidden || [];
|
|
124
|
+
if (required.includes(when.field) || forbidden.includes(when.field)) schemaError(path, "条件字段不能出现在 required 或 forbidden 中");
|
|
125
|
+
if (required.some((name) => forbidden.includes(name))) schemaError(path, "required 和 forbidden 不能包含相同字段");
|
|
126
|
+
return { kind: "when", field: when.field, is: when.is, required: required, forbidden: forbidden, message: message };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
function compilePresence(presence, message, node, path) {
|
|
130
|
+
assertObjectKeys(presence, new Set(["mode", "fields"]), path);
|
|
131
|
+
if (!PRESENCE_MODES.has(presence.mode)) schemaError([...path, "mode"], "无效");
|
|
132
|
+
assertFieldNames(node.fieldSet, presence.fields, [...path, "fields"]);
|
|
133
|
+
if (presence.fields.length < 2) schemaError([...path, "fields"], "至少需要两个字段");
|
|
134
|
+
return { kind: "presence", mode: presence.mode, fields: presence.fields, message: message };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
export function compileNode(schema, path, parents) {
|
|
138
|
+
if (!isPlainObject(schema)) schemaError(path, "必须是对象");
|
|
139
|
+
if (parents.has(schema)) schemaError(path, "存在循环引用");
|
|
140
|
+
parents.add(schema);
|
|
141
|
+
|
|
142
|
+
const isAnyOf = schema.anyOf !== undefined;
|
|
143
|
+
if (isAnyOf && schema.paramType !== undefined) schemaError(path, "不能同时配置 paramType 和 anyOf");
|
|
144
|
+
const allowedKeys = isAnyOf ? ANY_OF_KEYS : TYPE_KEYS[schema.paramType];
|
|
145
|
+
if (!isAnyOf && !SCHEMA_TYPES.has(schema.paramType)) schemaError([...path, "paramType"], "无效");
|
|
146
|
+
for (const key of Object.keys(schema)) if (!allowedKeys.has(key)) schemaError([...path, key], "是未知或不适用的配置");
|
|
147
|
+
|
|
148
|
+
assertBoolean(schema.optional, [...path, "optional"]);
|
|
149
|
+
const base = { optional: schema.optional === true, hasDefault: Object.hasOwn(schema, "paramDefault"), paramDefault: schema.paramDefault };
|
|
150
|
+
|
|
151
|
+
if (isAnyOf) {
|
|
152
|
+
if (!Array.isArray(schema.anyOf) || schema.anyOf.length < 2) schemaError([...path, "anyOf"], "至少需要两个分支");
|
|
153
|
+
const node = { ...base, kind: "anyOf", branches: schema.anyOf.map((branch, index) => compileNode(branch, [...path, "anyOf", index], parents)) };
|
|
154
|
+
parents.delete(schema);
|
|
155
|
+
compileDefault(node, path);
|
|
156
|
+
return node;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const node = { ...base, kind: schema.paramType };
|
|
160
|
+
if (["boolean", "integer", "number", "string"].includes(schema.paramType)) node.enumSet = compileEnum(schema, path);
|
|
161
|
+
|
|
162
|
+
if (schema.paramType === "integer" || schema.paramType === "number") {
|
|
163
|
+
assertValueBounds(schema, path);
|
|
164
|
+
if (schema.paramType === "integer") {
|
|
165
|
+
if (schema.minValue !== undefined && !Number.isSafeInteger(schema.minValue)) schemaError([...path, "minValue"], "必须是安全整数");
|
|
166
|
+
if (schema.maxValue !== undefined && !Number.isSafeInteger(schema.maxValue)) schemaError([...path, "maxValue"], "必须是安全整数");
|
|
167
|
+
}
|
|
168
|
+
node.minValue = schema.minValue;
|
|
169
|
+
node.maxValue = schema.maxValue;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (schema.paramType === "number") {
|
|
173
|
+
assertLimit(schema.precision, [...path, "precision"]);
|
|
174
|
+
assertLimit(schema.scale, [...path, "scale"]);
|
|
175
|
+
if (schema.precision === 0) schemaError([...path, "precision"], "必须大于 0");
|
|
176
|
+
if (schema.scale !== undefined && schema.precision === undefined) schemaError([...path, "scale"], "需要同时配置 precision");
|
|
177
|
+
if (schema.scale !== undefined && schema.scale > schema.precision) schemaError([...path, "scale"], "不能大于 precision");
|
|
178
|
+
node.precision = schema.precision;
|
|
179
|
+
node.scale = schema.scale;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (schema.paramType === "string") {
|
|
183
|
+
assertStringBounds(schema, path);
|
|
184
|
+
node.minValue = schema.minValue;
|
|
185
|
+
node.maxValue = schema.maxValue;
|
|
186
|
+
if (schema.pattern !== undefined && typeof schema.pattern !== "string") schemaError([...path, "pattern"], "必须是字符串");
|
|
187
|
+
if (schema.pattern !== undefined) {
|
|
188
|
+
try {
|
|
189
|
+
node.pattern = new RegExp(schema.pattern);
|
|
190
|
+
} catch {
|
|
191
|
+
schemaError([...path, "pattern"], "不是有效正则表达式");
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (schema.paramType === "array") {
|
|
197
|
+
assertItemBounds(schema, path);
|
|
198
|
+
assertBoolean(schema.unique, [...path, "unique"]);
|
|
199
|
+
node.minItem = schema.minItem;
|
|
200
|
+
node.maxItem = schema.maxItem;
|
|
201
|
+
node.unique = schema.unique === true;
|
|
202
|
+
node.items = schema.items === undefined ? null : compileNode(schema.items, [...path, "items"], parents);
|
|
203
|
+
if (schema.uniqueBy !== undefined) {
|
|
204
|
+
if (typeof schema.uniqueBy !== "string" || !schema.uniqueBy || schema.uniqueBy !== schema.uniqueBy.trim()) schemaError([...path, "uniqueBy"], "必须是非空无首尾空白字符串");
|
|
205
|
+
if (!node.items || node.items.kind !== "object" || !node.items.fieldSet.has(schema.uniqueBy)) schemaError([...path, "uniqueBy"], "必须引用 items.fields 中的字段");
|
|
206
|
+
if (node.items.fieldMap.get(schema.uniqueBy).optional) schemaError([...path, "uniqueBy"], "引用字段不能 optional");
|
|
207
|
+
node.uniqueBy = schema.uniqueBy;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (schema.paramType === "object") {
|
|
212
|
+
assertItemBounds(schema, path);
|
|
213
|
+
if (schema.fields !== undefined && !isPlainObject(schema.fields)) schemaError([...path, "fields"], "必须是对象");
|
|
214
|
+
if (schema.extra !== undefined && typeof schema.extra !== "boolean" && !isPlainObject(schema.extra)) schemaError([...path, "extra"], "必须是 boolean 或 Schema");
|
|
215
|
+
node.minItem = schema.minItem;
|
|
216
|
+
node.maxItem = schema.maxItem;
|
|
217
|
+
node.fields = Object.entries(schema.fields || {}).map(([key, child]) => ({ key: key, node: compileNode(child, [...path, "fields", key], parents) }));
|
|
218
|
+
node.fieldMap = new Map(node.fields.map((field) => [field.key, field.node]));
|
|
219
|
+
node.fieldSet = new Set(node.fieldMap.keys());
|
|
220
|
+
node.extra = isPlainObject(schema.extra) ? compileNode(schema.extra, [...path, "extra"], parents) : schema.extra === true;
|
|
221
|
+
const requiredCount = node.fields.filter((field) => !field.node.optional && !field.node.hasDefault).length;
|
|
222
|
+
if (node.maxItem !== undefined && node.maxItem < requiredCount) schemaError([...path, "maxItem"], `不能小于必填字段数量 ${requiredCount}`);
|
|
223
|
+
if (node.extra === false && node.minItem !== undefined && node.minItem > node.fields.length) schemaError([...path, "minItem"], `不能大于 fields 数量 ${node.fields.length}`);
|
|
224
|
+
node.constraints = compileConstraints(schema, node, path);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (schema.paramType === "record") {
|
|
228
|
+
assertItemBounds(schema, path);
|
|
229
|
+
if (schema.values === undefined) schemaError([...path, "values"], "为必填项");
|
|
230
|
+
node.minItem = schema.minItem;
|
|
231
|
+
node.maxItem = schema.maxItem;
|
|
232
|
+
node.keys = schema.keys === undefined ? null : compileNode(schema.keys, [...path, "keys"], parents);
|
|
233
|
+
if (node.keys && node.keys.kind !== "string") schemaError([...path, "keys"], "必须是 string Schema");
|
|
234
|
+
node.values = compileNode(schema.values, [...path, "values"], parents);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
parents.delete(schema);
|
|
238
|
+
compileDefault(node, path);
|
|
239
|
+
return node;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function compileDefault(node, path) {
|
|
243
|
+
if (!node.hasDefault) return;
|
|
244
|
+
if (node.kind === "function") schemaError([...path, "paramDefault"], "function 不允许默认值");
|
|
245
|
+
const result = parseNode(node.paramDefault, { ...node, hasDefault: false }, [], false);
|
|
246
|
+
if (!result.ok) schemaError([...path, "paramDefault"], `不符合参数规则: ${result.issues.map((issue) => issue.message).join(";")}`);
|
|
247
|
+
node.paramDefault = result.data;
|
|
248
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { compileNode } from "./compiler.js";
|
|
2
|
+
import { parseNode } from "./parser.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* 编译固定 JSON 参数协议。编译阶段验证 Schema,返回可重复执行的解析函数。
|
|
6
|
+
*/
|
|
7
|
+
export function compile(schema) {
|
|
8
|
+
const node = compileNode(schema, [], new Set());
|
|
9
|
+
return (value) => parseNode(value, node, []);
|
|
10
|
+
}
|
|
@@ -0,0 +1,173 @@
|
|
|
1
|
+
import { isPlainObject } from "./util.js";
|
|
2
|
+
|
|
3
|
+
function addIssue(issues, path, code, message) {
|
|
4
|
+
issues.push({ path: path, code: code, message: message });
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
function typeMessage(kind) {
|
|
8
|
+
if (kind === "boolean") return "必须是 boolean";
|
|
9
|
+
if (kind === "integer") return "必须是安全整数";
|
|
10
|
+
if (kind === "number") return "必须是有限数字";
|
|
11
|
+
if (kind === "string") return "必须是字符串";
|
|
12
|
+
if (kind === "function") return "必须是函数";
|
|
13
|
+
if (kind === "array") return "必须是数组";
|
|
14
|
+
return "必须是对象";
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function checkNumberPrecision(value, node, path, issues) {
|
|
18
|
+
if (node.precision === undefined) return;
|
|
19
|
+
const text = String(value).replace(/^-/, "");
|
|
20
|
+
if (/e/i.test(text)) {
|
|
21
|
+
addIssue(issues, path, "precision", "不能使用科学计数法");
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
const [integerPart, decimalPart = ""] = text.split(".");
|
|
25
|
+
if (decimalPart.length > (node.scale || 0)) addIssue(issues, path, "scale", `小数位不能超过${node.scale || 0}位`);
|
|
26
|
+
if (integerPart.replace(/^0+/, "").length + decimalPart.length > node.precision) addIssue(issues, path, "precision", `总位数不能超过${node.precision}位`);
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function cloneDefault(value) {
|
|
30
|
+
return value && typeof value === "object" ? structuredClone(value) : value;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function success(data) {
|
|
34
|
+
return { ok: true, data: data, issues: [] };
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function failure(issues) {
|
|
38
|
+
return { ok: false, issues: issues };
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export function parseNode(value, node, path, useDefault = true) {
|
|
42
|
+
if (value === undefined && useDefault && node.hasDefault) value = cloneDefault(node.paramDefault);
|
|
43
|
+
if (value === undefined) return node.optional ? success() : failure([{ path: path, code: "missing", message: "为必填项" }]);
|
|
44
|
+
if (value === null) return failure([{ path: path, code: "null", message: "不允许为 null" }]);
|
|
45
|
+
|
|
46
|
+
if (node.kind === "anyOf") {
|
|
47
|
+
for (const branch of node.branches) {
|
|
48
|
+
const result = parseNode(value, branch, path);
|
|
49
|
+
if (result.ok) return result;
|
|
50
|
+
}
|
|
51
|
+
return failure([{ path: path, code: "anyOf", message: "不符合任一允许类型" }]);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (node.kind === "any") return success(value);
|
|
55
|
+
if (node.kind === "boolean" && typeof value !== "boolean") return failure([{ path: path, code: "type", message: typeMessage(node.kind) }]);
|
|
56
|
+
if (node.kind === "integer" && !Number.isSafeInteger(value)) return failure([{ path: path, code: "type", message: typeMessage(node.kind) }]);
|
|
57
|
+
if (node.kind === "number" && (typeof value !== "number" || !Number.isFinite(value))) return failure([{ path: path, code: "type", message: typeMessage(node.kind) }]);
|
|
58
|
+
if (node.kind === "string" && typeof value !== "string") return failure([{ path: path, code: "type", message: typeMessage(node.kind) }]);
|
|
59
|
+
if (node.kind === "function" && typeof value !== "function") return failure([{ path: path, code: "type", message: typeMessage(node.kind) }]);
|
|
60
|
+
if (node.kind === "array" && !Array.isArray(value)) return failure([{ path: path, code: "type", message: typeMessage(node.kind) }]);
|
|
61
|
+
if ((node.kind === "object" || node.kind === "record") && !isPlainObject(value)) return failure([{ path: path, code: "type", message: typeMessage(node.kind) }]);
|
|
62
|
+
|
|
63
|
+
const issues = [];
|
|
64
|
+
if (node.enumSet && !node.enumSet.has(value)) addIssue(issues, path, "enum", `仅允许 ${[...node.enumSet].join("|")}`);
|
|
65
|
+
|
|
66
|
+
if (node.kind === "integer" || node.kind === "number") {
|
|
67
|
+
if (node.minValue !== undefined && value < node.minValue) addIssue(issues, path, "minValue", `不能小于${node.minValue}`);
|
|
68
|
+
if (node.maxValue !== undefined && value > node.maxValue) addIssue(issues, path, "maxValue", `不能大于${node.maxValue}`);
|
|
69
|
+
if (node.kind === "number") checkNumberPrecision(value, node, path, issues);
|
|
70
|
+
return issues.length > 0 ? failure(issues) : success(value);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
if (node.kind === "string") {
|
|
74
|
+
if (value !== value.trim()) addIssue(issues, path, "trim", "不允许首尾空白");
|
|
75
|
+
if (node.minValue !== undefined && value.length < node.minValue) addIssue(issues, path, "minValue", `长度不能少于${node.minValue}个字符`);
|
|
76
|
+
if (node.maxValue !== undefined && value.length > node.maxValue) addIssue(issues, path, "maxValue", `长度不能超过${node.maxValue}个字符`);
|
|
77
|
+
if (node.pattern && !node.pattern.test(value)) addIssue(issues, path, "pattern", "格式不正确");
|
|
78
|
+
return issues.length > 0 ? failure(issues) : success(value);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (node.kind === "array") {
|
|
82
|
+
if (node.minItem !== undefined && value.length < node.minItem) addIssue(issues, path, "minItem", `至少需要${node.minItem}项`);
|
|
83
|
+
if (node.maxItem !== undefined && value.length > node.maxItem) addIssue(issues, path, "maxItem", `最多允许${node.maxItem}项`);
|
|
84
|
+
if (node.unique && new Set(value).size !== value.length) addIssue(issues, path, "unique", "数组项不能重复");
|
|
85
|
+
if (node.uniqueBy) {
|
|
86
|
+
const seen = new Set();
|
|
87
|
+
value.forEach((item, index) => {
|
|
88
|
+
if (!isPlainObject(item) || item[node.uniqueBy] === undefined) return;
|
|
89
|
+
if (seen.has(item[node.uniqueBy])) addIssue(issues, [...path, index, node.uniqueBy], "uniqueBy", `${node.uniqueBy} ${item[node.uniqueBy]} 重复`);
|
|
90
|
+
else seen.add(item[node.uniqueBy]);
|
|
91
|
+
});
|
|
92
|
+
}
|
|
93
|
+
const data = [];
|
|
94
|
+
if (node.items) {
|
|
95
|
+
value.forEach((item, index) => {
|
|
96
|
+
const result = parseNode(item, node.items, [...path, index]);
|
|
97
|
+
if (result.ok) data.push(result.data);
|
|
98
|
+
else issues.push(...result.issues);
|
|
99
|
+
});
|
|
100
|
+
} else data.push(...value);
|
|
101
|
+
return issues.length > 0 ? failure(issues) : success(data);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (node.kind === "object") return parseObject(value, node, path);
|
|
105
|
+
if (node.kind === "record") {
|
|
106
|
+
const keys = Object.keys(value);
|
|
107
|
+
if (node.minItem !== undefined && keys.length < node.minItem) addIssue(issues, path, "minItem", `至少需要${node.minItem}项`);
|
|
108
|
+
if (node.maxItem !== undefined && keys.length > node.maxItem) addIssue(issues, path, "maxItem", `最多允许${node.maxItem}项`);
|
|
109
|
+
const data = {};
|
|
110
|
+
for (const [key, item] of Object.entries(value)) {
|
|
111
|
+
if (node.keys) {
|
|
112
|
+
const keyResult = parseNode(key, node.keys, [...path, key]);
|
|
113
|
+
if (!keyResult.ok) issues.push(...keyResult.issues);
|
|
114
|
+
}
|
|
115
|
+
const valueResult = parseNode(item, node.values, [...path, key]);
|
|
116
|
+
if (valueResult.ok) data[key] = valueResult.data;
|
|
117
|
+
else issues.push(...valueResult.issues);
|
|
118
|
+
}
|
|
119
|
+
return issues.length > 0 ? failure(issues) : success(data);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return success(value);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function parseObject(value, node, path) {
|
|
126
|
+
const issues = [];
|
|
127
|
+
const data = {};
|
|
128
|
+
const validFields = new Set();
|
|
129
|
+
for (const { key, node: child } of node.fields) {
|
|
130
|
+
const result = parseNode(value[key], child, [...path, key]);
|
|
131
|
+
if (result.ok) {
|
|
132
|
+
if (result.data !== undefined) data[key] = result.data;
|
|
133
|
+
validFields.add(key);
|
|
134
|
+
} else issues.push(...result.issues);
|
|
135
|
+
}
|
|
136
|
+
for (const [key, item] of Object.entries(value)) {
|
|
137
|
+
if (node.fieldSet.has(key)) continue;
|
|
138
|
+
if (node.extra === false) addIssue(issues, [...path, key], "extra", "不允许出现");
|
|
139
|
+
else if (node.extra === true) data[key] = item;
|
|
140
|
+
else {
|
|
141
|
+
const result = parseNode(item, node.extra, [...path, key]);
|
|
142
|
+
if (result.ok) data[key] = result.data;
|
|
143
|
+
else issues.push(...result.issues);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
const itemCount = Object.keys(data).length;
|
|
147
|
+
if (node.minItem !== undefined && itemCount < node.minItem) addIssue(issues, path, "minItem", `至少需要${node.minItem}项`);
|
|
148
|
+
if (node.maxItem !== undefined && itemCount > node.maxItem) addIssue(issues, path, "maxItem", `最多允许${node.maxItem}项`);
|
|
149
|
+
validateConstraints(data, node, path, validFields, issues);
|
|
150
|
+
return issues.length > 0 ? failure(issues) : success(data);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
function validateConstraints(data, node, path, validFields, issues) {
|
|
154
|
+
for (const constraint of node.constraints) {
|
|
155
|
+
if (constraint.kind === "compare") {
|
|
156
|
+
if (!validFields.has(constraint.left) || !validFields.has(constraint.right) || data[constraint.left] === undefined || data[constraint.right] === undefined) continue;
|
|
157
|
+
const left = data[constraint.left];
|
|
158
|
+
const right = data[constraint.right];
|
|
159
|
+
const passed = constraint.operator === "eq" ? Object.is(left, right) : constraint.operator === "neq" ? !Object.is(left, right) : constraint.operator === "gt" ? left > right : constraint.operator === "gte" ? left >= right : constraint.operator === "lt" ? left < right : left <= right;
|
|
160
|
+
if (!passed) addIssue(issues, [...path, constraint.right], "constraint.compare", constraint.message || "字段关系不符合要求");
|
|
161
|
+
continue;
|
|
162
|
+
}
|
|
163
|
+
if (constraint.kind === "when") {
|
|
164
|
+
if (!validFields.has(constraint.field) || !Object.is(data[constraint.field], constraint.is)) continue;
|
|
165
|
+
for (const field of constraint.required) if (data[field] === undefined) addIssue(issues, [...path, field], "constraint.when", constraint.message || "条件成立时为必填项");
|
|
166
|
+
for (const field of constraint.forbidden) if (data[field] !== undefined) addIssue(issues, [...path, field], "constraint.when", constraint.message || "条件成立时不允许出现");
|
|
167
|
+
continue;
|
|
168
|
+
}
|
|
169
|
+
const existsCount = constraint.fields.filter((field) => data[field] !== undefined).length;
|
|
170
|
+
const passed = constraint.mode === "together" ? existsCount === 0 || existsCount === constraint.fields.length : constraint.mode === "atLeastOne" ? existsCount >= 1 : existsCount === 1;
|
|
171
|
+
if (!passed) addIssue(issues, path, "constraint.presence", constraint.message || "字段存在关系不符合要求");
|
|
172
|
+
}
|
|
173
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "befly",
|
|
3
|
-
"version": "3.76.
|
|
3
|
+
"version": "3.76.3",
|
|
4
4
|
"gitHead": "49c39d36695036e85fc64083cc43c1652fff96cb",
|
|
5
5
|
"private": false,
|
|
6
6
|
"description": "Befly - 为 Bun 专属打造的 JavaScript API 接口框架核心引擎",
|
|
@@ -23,16 +23,17 @@
|
|
|
23
23
|
"checks",
|
|
24
24
|
"configs",
|
|
25
25
|
"hooks",
|
|
26
|
-
"
|
|
26
|
+
"libs",
|
|
27
27
|
"plugins",
|
|
28
28
|
"router",
|
|
29
29
|
"schemas",
|
|
30
30
|
"scripts",
|
|
31
|
-
"sql",
|
|
32
31
|
"sync",
|
|
33
32
|
"tables",
|
|
34
33
|
"utils",
|
|
35
34
|
"index.js",
|
|
35
|
+
"Befly.js",
|
|
36
|
+
"exports.js",
|
|
36
37
|
"paths.js",
|
|
37
38
|
"LICENSE",
|
|
38
39
|
"README.md"
|
|
@@ -46,6 +47,10 @@
|
|
|
46
47
|
".": {
|
|
47
48
|
"import": "./index.js",
|
|
48
49
|
"default": "./index.js"
|
|
50
|
+
},
|
|
51
|
+
"./exports": {
|
|
52
|
+
"import": "./exports.js",
|
|
53
|
+
"default": "./exports.js"
|
|
49
54
|
}
|
|
50
55
|
},
|
|
51
56
|
"publishConfig": {
|
|
@@ -55,11 +60,8 @@
|
|
|
55
60
|
"scripts": {
|
|
56
61
|
"test": "bun test"
|
|
57
62
|
},
|
|
58
|
-
"dependencies": {
|
|
59
|
-
"pathe": "^2.0.3",
|
|
60
|
-
"picomatch": "^4.0.5"
|
|
61
|
-
},
|
|
63
|
+
"dependencies": {},
|
|
62
64
|
"engines": {
|
|
63
|
-
"bun": ">=1.
|
|
65
|
+
"bun": ">=1.4.0"
|
|
64
66
|
}
|
|
65
67
|
}
|
package/paths.js
CHANGED
|
@@ -10,10 +10,9 @@
|
|
|
10
10
|
*
|
|
11
11
|
*/
|
|
12
12
|
|
|
13
|
+
import { basename, dirname, isAbsolute, join, resolve } from "node:path";
|
|
13
14
|
import { fileURLToPath } from "node:url";
|
|
14
15
|
|
|
15
|
-
import { dirname, isAbsolute, join, normalize, resolve } from "pathe";
|
|
16
|
-
|
|
17
16
|
// 当前文件的路径信息
|
|
18
17
|
const moduleFilePath = fileURLToPath(import.meta.url);
|
|
19
18
|
const moduleDir = dirname(moduleFilePath);
|
|
@@ -30,7 +29,7 @@ export const coreDir = moduleDir;
|
|
|
30
29
|
* Core 框架 dist 目录
|
|
31
30
|
* @description 源码态为 packages/core/dist;dist 运行态为 packages/core/dist
|
|
32
31
|
*/
|
|
33
|
-
export const coreDistDir =
|
|
32
|
+
export const coreDistDir = basename(moduleDir) === "dist" ? moduleDir : join(moduleDir, "dist");
|
|
34
33
|
|
|
35
34
|
/**
|
|
36
35
|
* Core 框架检查目录
|
package/plugins/cache.js
CHANGED
|
@@ -2,12 +2,13 @@ import { PLUGIN_ORDER } from "../configs/constConfig.js";
|
|
|
2
2
|
/**
|
|
3
3
|
* 负责在服务器启动时缓存接口、菜单和角色权限到 Redis
|
|
4
4
|
*/
|
|
5
|
-
import { CacheHelper } from "../
|
|
5
|
+
import { CacheHelper } from "../libs/cacheHelper.js";
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* 缓存插件
|
|
9
9
|
*/
|
|
10
10
|
export default {
|
|
11
|
+
name: "缓存",
|
|
11
12
|
order: PLUGIN_ORDER.CACHE,
|
|
12
13
|
handler: async function (befly) {
|
|
13
14
|
return new CacheHelper({ mysql: befly.mysql, redis: befly.redis });
|
package/plugins/email.js
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import { Logger } from "#befly/
|
|
2
|
-
import { sendSmtpTextMail } from "#befly/
|
|
3
|
-
import { hasEmailConfig } from "#befly/utils/email.js";
|
|
1
|
+
import { Logger } from "#befly/libs/logger/index.js";
|
|
2
|
+
import { sendSmtpTextMail } from "#befly/libs/smtpText.js";
|
|
4
3
|
|
|
5
4
|
import { PLUGIN_ORDER } from "../configs/constConfig.js";
|
|
6
5
|
|
|
7
6
|
export default {
|
|
7
|
+
name: "邮件",
|
|
8
8
|
order: PLUGIN_ORDER.EMAIL,
|
|
9
9
|
handler: async function (befly) {
|
|
10
10
|
const config = befly?.config?.email || {};
|
|
11
|
-
const enabled =
|
|
11
|
+
const enabled = config.host && config.user && config.pass;
|
|
12
12
|
|
|
13
13
|
const sendMail = async function (options) {
|
|
14
14
|
if (!enabled) {
|
package/plugins/mysql.js
CHANGED
|
@@ -1,30 +1,20 @@
|
|
|
1
|
+
import { createMysql } from "#befly/libs/mysql/index.js";
|
|
2
|
+
|
|
1
3
|
import { PLUGIN_ORDER } from "../configs/constConfig.js";
|
|
2
|
-
/**
|
|
3
|
-
* 数据库插件
|
|
4
|
-
* 初始化数据库连接和 SQL 管理器
|
|
5
|
-
*/
|
|
6
|
-
import { Connect } from "../lib/connect.js";
|
|
7
|
-
import { DbHelper } from "../lib/dbHelper.js";
|
|
8
4
|
import { createError } from "../utils/error.js";
|
|
9
5
|
|
|
10
6
|
/**
|
|
11
7
|
* 数据库插件
|
|
12
8
|
*/
|
|
13
9
|
export default {
|
|
10
|
+
name: "MySQL",
|
|
14
11
|
order: PLUGIN_ORDER.MYSQL,
|
|
15
12
|
handler: async function (befly) {
|
|
16
13
|
try {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
redis: befly.redis,
|
|
20
|
-
dbName: befly.config?.mysql?.database,
|
|
21
|
-
sql: Connect.getMysql(),
|
|
22
|
-
beflyMode: befly.config?.mysql?.beflyMode,
|
|
23
|
-
queryTimeout: befly.config?.mysql?.queryTimeout,
|
|
14
|
+
return await createMysql({
|
|
15
|
+
...befly.config.mysql,
|
|
24
16
|
tables: befly.tables
|
|
25
17
|
});
|
|
26
|
-
|
|
27
|
-
return dbManager;
|
|
28
18
|
} catch (error) {
|
|
29
19
|
throw createError("数据库初始化失败", { cause: error, code: "runtime", subsystem: "db", operation: "init" });
|
|
30
20
|
}
|
package/plugins/redis.js
CHANGED
|
@@ -1,25 +1,17 @@
|
|
|
1
|
+
import { createRedis } from "#befly/libs/redis/index.js";
|
|
2
|
+
|
|
1
3
|
import { PLUGIN_ORDER } from "../configs/constConfig.js";
|
|
2
|
-
/**
|
|
3
|
-
* Redis 插件
|
|
4
|
-
* 初始化 Redis 连接和助手工具
|
|
5
|
-
*/
|
|
6
|
-
import { Connect } from "../lib/connect.js";
|
|
7
|
-
import { RedisHelper } from "../lib/redisHelper.js";
|
|
8
4
|
import { createError } from "../utils/error.js";
|
|
9
5
|
|
|
10
6
|
/**
|
|
11
7
|
* Redis 插件
|
|
12
8
|
*/
|
|
13
9
|
export default {
|
|
10
|
+
name: "Redis",
|
|
14
11
|
order: PLUGIN_ORDER.REDIS,
|
|
15
12
|
handler: async function (befly) {
|
|
16
|
-
const redisPrefix = befly.config?.redis?.prefix;
|
|
17
13
|
try {
|
|
18
|
-
|
|
19
|
-
Connect.getRedis();
|
|
20
|
-
|
|
21
|
-
// 返回 RedisHelper 实例
|
|
22
|
-
return new RedisHelper(redisPrefix);
|
|
14
|
+
return await createRedis(befly.config.redis);
|
|
23
15
|
} catch (error) {
|
|
24
16
|
throw createError("Redis 初始化失败", { cause: error, code: "runtime", subsystem: "redis", operation: "init" });
|
|
25
17
|
}
|