befly 3.21.2 → 3.22.1
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/admin/list.js +1 -1
- package/apis/admin/upd.js +2 -2
- package/apis/dict/upd.js +1 -1
- package/apis/dictType/upd.js +1 -1
- package/apis/role/all.js +1 -1
- package/apis/role/list.js +1 -1
- package/apis/role/upd.js +1 -1
- package/apis/source/imageList.js +27 -0
- package/apis/upload/file.js +105 -0
- package/checks/config.js +2 -0
- package/configs/beflyConfig.json +2 -1
- package/configs/beflyMenus.json +12 -0
- package/index.js +5 -5
- package/lib/dbHelper.js +170 -751
- package/lib/dbParse.js +1045 -0
- package/lib/dbUtil.js +52 -508
- package/lib/sqlBuilder.js +78 -294
- package/package.json +2 -2
- package/paths.js +2 -2
- package/router/static.js +2 -1
- package/sql/befly.sql +20 -0
- package/tables/file.json +81 -0
- package/utils/datetime.js +29 -32
package/lib/sqlBuilder.js
CHANGED
|
@@ -3,20 +3,8 @@
|
|
|
3
3
|
* 提供链式 API 构建 SQL 查询
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import {
|
|
7
|
-
import { isNonEmptyString
|
|
8
|
-
|
|
9
|
-
function createModel() {
|
|
10
|
-
return {
|
|
11
|
-
select: [],
|
|
12
|
-
from: null,
|
|
13
|
-
where: { type: "group", join: "AND", items: [] },
|
|
14
|
-
joins: [],
|
|
15
|
-
orderBy: [],
|
|
16
|
-
limit: null,
|
|
17
|
-
offset: null
|
|
18
|
-
};
|
|
19
|
-
}
|
|
6
|
+
import { escapeField, escapeTable, resolveQuoteIdent } from "./dbUtil.js";
|
|
7
|
+
import { isNonEmptyString } from "../utils/is.js";
|
|
20
8
|
|
|
21
9
|
/**
|
|
22
10
|
* SQL 构建器类
|
|
@@ -27,188 +15,37 @@ export class SqlBuilder {
|
|
|
27
15
|
|
|
28
16
|
constructor(options = {}) {
|
|
29
17
|
this._quoteIdent = resolveQuoteIdent(options);
|
|
30
|
-
this._model =
|
|
18
|
+
this._model = {
|
|
19
|
+
select: [],
|
|
20
|
+
from: null,
|
|
21
|
+
where: { type: "group", join: "AND", items: [] },
|
|
22
|
+
joins: [],
|
|
23
|
+
orderBy: [],
|
|
24
|
+
limit: null,
|
|
25
|
+
offset: null
|
|
26
|
+
};
|
|
31
27
|
}
|
|
32
28
|
|
|
33
29
|
/**
|
|
34
30
|
* 重置构建器状态
|
|
35
31
|
*/
|
|
36
32
|
reset() {
|
|
37
|
-
this._model =
|
|
33
|
+
this._model = {
|
|
34
|
+
select: [],
|
|
35
|
+
from: null,
|
|
36
|
+
where: { type: "group", join: "AND", items: [] },
|
|
37
|
+
joins: [],
|
|
38
|
+
orderBy: [],
|
|
39
|
+
limit: null,
|
|
40
|
+
offset: null
|
|
41
|
+
};
|
|
38
42
|
return this;
|
|
39
43
|
}
|
|
40
44
|
|
|
41
|
-
_appendWhereNode(root, node) {
|
|
42
|
-
if (node) {
|
|
43
|
-
root.items.push(node);
|
|
44
|
-
}
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
_buildArrayOperatorNode(fieldName, operator, value, errorFactory, emptyMessage) {
|
|
48
|
-
if (!Array.isArray(value)) {
|
|
49
|
-
throw new Error(errorFactory(operator), {
|
|
50
|
-
cause: null,
|
|
51
|
-
code: "validation"
|
|
52
|
-
});
|
|
53
|
-
}
|
|
54
|
-
if (value.length === 0) {
|
|
55
|
-
throw new Error(emptyMessage, {
|
|
56
|
-
cause: null,
|
|
57
|
-
code: "validation"
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
return { type: "op", field: fieldName, operator: operator, value: value };
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
_buildRangeOrNullOperatorNode(fieldName, operator, value) {
|
|
65
|
-
if (operator === "$between" || operator === "$notBetween") {
|
|
66
|
-
if (Array.isArray(value) && value.length === 2) {
|
|
67
|
-
return { type: "op", field: fieldName, operator: operator, value: value };
|
|
68
|
-
}
|
|
69
|
-
return null;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
if (value === true) {
|
|
73
|
-
return { type: "op", field: fieldName, operator: operator, value: value };
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
return null;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
_buildOperatorNode(fieldName, operator, value) {
|
|
80
|
-
switch (operator) {
|
|
81
|
-
case "$in":
|
|
82
|
-
return this._buildArrayOperatorNode(fieldName, operator, value, (currentOperator) => `$in 操作符的值必须是数组 (operator: ${currentOperator})`, "$in 操作符的数组不能为空。提示:空数组会导致查询永远不匹配任何记录,这通常不是预期行为。请检查查询条件或移除该字段。");
|
|
83
|
-
case "$nin":
|
|
84
|
-
case "$notIn":
|
|
85
|
-
return this._buildArrayOperatorNode(fieldName, operator, value, (currentOperator) => `$nin/$notIn 操作符的值必须是数组 (operator: ${currentOperator})`, "$nin/$notIn 操作符的数组不能为空。提示:空数组会导致查询匹配所有记录,这通常不是预期行为。请检查查询条件或移除该字段。");
|
|
86
|
-
case "$between":
|
|
87
|
-
case "$notBetween":
|
|
88
|
-
case "$null":
|
|
89
|
-
case "$notNull":
|
|
90
|
-
return this._buildRangeOrNullOperatorNode(fieldName, operator, value);
|
|
91
|
-
case "$like":
|
|
92
|
-
case "like":
|
|
93
|
-
case "$leftLike":
|
|
94
|
-
case "leftLike":
|
|
95
|
-
case "$rightLike":
|
|
96
|
-
case "rightLike":
|
|
97
|
-
if (isString(value) && value.trim() === "") {
|
|
98
|
-
return null;
|
|
99
|
-
}
|
|
100
|
-
validateParam(value);
|
|
101
|
-
return { type: "op", field: fieldName, operator: operator, value: value };
|
|
102
|
-
default:
|
|
103
|
-
validateParam(value);
|
|
104
|
-
return { type: "op", field: fieldName, operator: operator, value: value };
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
_appendFieldCondition(group, key, value) {
|
|
109
|
-
if (key.includes("$")) {
|
|
110
|
-
const lastDollarIndex = key.lastIndexOf("$");
|
|
111
|
-
this._appendWhereNode(group, this._buildOperatorNode(key.substring(0, lastDollarIndex), `$${key.substring(lastDollarIndex + 1)}`, value));
|
|
112
|
-
return;
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
if (value && typeof value === "object" && !Array.isArray(value)) {
|
|
116
|
-
for (const [operator, operatorValue] of Object.entries(value)) {
|
|
117
|
-
this._appendWhereNode(group, this._buildOperatorNode(key, operator, operatorValue));
|
|
118
|
-
}
|
|
119
|
-
return;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
this._appendWhereNode(group, this._buildOperatorNode(key, "=", value));
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
_appendAndConditions(group, value) {
|
|
126
|
-
if (!Array.isArray(value)) {
|
|
127
|
-
return;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
for (const condition of value) {
|
|
131
|
-
if (!condition || typeof condition !== "object" || Array.isArray(condition)) {
|
|
132
|
-
continue;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
const sub = this._parseWhereObject(condition);
|
|
136
|
-
if (sub.items.length === 0) {
|
|
137
|
-
continue;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
if (sub.join === "AND") {
|
|
141
|
-
for (const item of sub.items) {
|
|
142
|
-
group.items.push(item);
|
|
143
|
-
}
|
|
144
|
-
continue;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
group.items.push(sub);
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
|
|
151
|
-
_appendOrConditions(group, value) {
|
|
152
|
-
if (!Array.isArray(value)) {
|
|
153
|
-
return;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
const orGroup = { type: "group", join: "OR", items: [] };
|
|
157
|
-
for (const condition of value) {
|
|
158
|
-
if (!condition || typeof condition !== "object" || Array.isArray(condition)) {
|
|
159
|
-
continue;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
const sub = this._parseWhereObject(condition);
|
|
163
|
-
if (sub.items.length > 0) {
|
|
164
|
-
orGroup.items.push(sub);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
if (orGroup.items.length > 0) {
|
|
169
|
-
group.items.push(orGroup);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
_parseWhereObject(whereObj) {
|
|
174
|
-
if (!whereObj || typeof whereObj !== "object") {
|
|
175
|
-
return { type: "group", join: "AND", items: [] };
|
|
176
|
-
}
|
|
177
|
-
|
|
178
|
-
const group = { type: "group", join: "AND", items: [] };
|
|
179
|
-
|
|
180
|
-
for (const [key, value] of Object.entries(whereObj)) {
|
|
181
|
-
if (value === undefined) {
|
|
182
|
-
continue;
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
if (key === "$and") {
|
|
186
|
-
this._appendAndConditions(group, value);
|
|
187
|
-
continue;
|
|
188
|
-
}
|
|
189
|
-
|
|
190
|
-
if (key === "$or") {
|
|
191
|
-
this._appendOrConditions(group, value);
|
|
192
|
-
continue;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
this._appendFieldCondition(group, key, value);
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
return group;
|
|
199
|
-
}
|
|
200
|
-
|
|
201
|
-
_pushWhereParams(target, source) {
|
|
202
|
-
for (const param of source) {
|
|
203
|
-
target.push(param);
|
|
204
|
-
}
|
|
205
|
-
}
|
|
206
|
-
|
|
207
45
|
_compileOperatorNode(node) {
|
|
208
46
|
const escapedField = escapeField(node.field, this._quoteIdent);
|
|
209
47
|
|
|
210
|
-
if (node.operator === "$
|
|
211
|
-
validateParam(node.value);
|
|
48
|
+
if (node.operator === "$not") {
|
|
212
49
|
return { sql: `${escapedField} != ?`, params: [node.value] };
|
|
213
50
|
}
|
|
214
51
|
|
|
@@ -219,50 +56,42 @@ export class SqlBuilder {
|
|
|
219
56
|
};
|
|
220
57
|
}
|
|
221
58
|
|
|
222
|
-
if (node.operator === "$
|
|
59
|
+
if (node.operator === "$notIn") {
|
|
223
60
|
return {
|
|
224
61
|
sql: `${escapedField} NOT IN (${node.value.map(() => "?").join(",")})`,
|
|
225
62
|
params: node.value.slice()
|
|
226
63
|
};
|
|
227
64
|
}
|
|
228
65
|
|
|
229
|
-
if (node.operator === "$like"
|
|
230
|
-
validateParam(node.value);
|
|
66
|
+
if (node.operator === "$like") {
|
|
231
67
|
return { sql: `${escapedField} LIKE ?`, params: [`%${String(node.value)}%`] };
|
|
232
68
|
}
|
|
233
69
|
|
|
234
|
-
if (node.operator === "$leftLike"
|
|
235
|
-
validateParam(node.value);
|
|
70
|
+
if (node.operator === "$leftLike") {
|
|
236
71
|
return { sql: `${escapedField} LIKE ?`, params: [`%${String(node.value)}`] };
|
|
237
72
|
}
|
|
238
73
|
|
|
239
|
-
if (node.operator === "$rightLike"
|
|
240
|
-
validateParam(node.value);
|
|
74
|
+
if (node.operator === "$rightLike") {
|
|
241
75
|
return { sql: `${escapedField} LIKE ?`, params: [`${String(node.value)}%`] };
|
|
242
76
|
}
|
|
243
77
|
|
|
244
78
|
if (node.operator === "$notLike") {
|
|
245
|
-
validateParam(node.value);
|
|
246
79
|
return { sql: `${escapedField} NOT LIKE ?`, params: [node.value] };
|
|
247
80
|
}
|
|
248
81
|
|
|
249
82
|
if (node.operator === "$gt") {
|
|
250
|
-
validateParam(node.value);
|
|
251
83
|
return { sql: `${escapedField} > ?`, params: [node.value] };
|
|
252
84
|
}
|
|
253
85
|
|
|
254
86
|
if (node.operator === "$gte") {
|
|
255
|
-
validateParam(node.value);
|
|
256
87
|
return { sql: `${escapedField} >= ?`, params: [node.value] };
|
|
257
88
|
}
|
|
258
89
|
|
|
259
90
|
if (node.operator === "$lt") {
|
|
260
|
-
validateParam(node.value);
|
|
261
91
|
return { sql: `${escapedField} < ?`, params: [node.value] };
|
|
262
92
|
}
|
|
263
93
|
|
|
264
94
|
if (node.operator === "$lte") {
|
|
265
|
-
validateParam(node.value);
|
|
266
95
|
return { sql: `${escapedField} <= ?`, params: [node.value] };
|
|
267
96
|
}
|
|
268
97
|
|
|
@@ -282,7 +111,6 @@ export class SqlBuilder {
|
|
|
282
111
|
return { sql: `${escapedField} IS NOT NULL`, params: [] };
|
|
283
112
|
}
|
|
284
113
|
|
|
285
|
-
validateParam(node.value);
|
|
286
114
|
return { sql: `${escapedField} = ?`, params: [node.value] };
|
|
287
115
|
}
|
|
288
116
|
|
|
@@ -292,7 +120,10 @@ export class SqlBuilder {
|
|
|
292
120
|
}
|
|
293
121
|
|
|
294
122
|
if (node.type === "raw") {
|
|
295
|
-
|
|
123
|
+
if (Array.isArray(node.params)) {
|
|
124
|
+
return { sql: node.sql, params: node.params.slice() };
|
|
125
|
+
}
|
|
126
|
+
return { sql: node.sql, params: [] };
|
|
296
127
|
}
|
|
297
128
|
|
|
298
129
|
if (node.type === "op") {
|
|
@@ -320,7 +151,9 @@ export class SqlBuilder {
|
|
|
320
151
|
}
|
|
321
152
|
|
|
322
153
|
parts.push(clause);
|
|
323
|
-
|
|
154
|
+
for (const param of built.params) {
|
|
155
|
+
params.push(param);
|
|
156
|
+
}
|
|
324
157
|
}
|
|
325
158
|
|
|
326
159
|
return { sql: parts.join(` ${node.join} `), params: params };
|
|
@@ -345,9 +178,7 @@ export class SqlBuilder {
|
|
|
345
178
|
return this;
|
|
346
179
|
}
|
|
347
180
|
|
|
348
|
-
|
|
349
|
-
this._model.select.push({ type: "field", value: fields });
|
|
350
|
-
}
|
|
181
|
+
this._model.select.push({ type: "field", value: fields });
|
|
351
182
|
return this;
|
|
352
183
|
}
|
|
353
184
|
|
|
@@ -378,18 +209,20 @@ export class SqlBuilder {
|
|
|
378
209
|
/**
|
|
379
210
|
* WHERE 条件
|
|
380
211
|
*/
|
|
381
|
-
where(conditionOrField
|
|
382
|
-
if (
|
|
383
|
-
|
|
384
|
-
if (node.items.length > 0) {
|
|
385
|
-
this._model.where.items.push(node);
|
|
386
|
-
}
|
|
212
|
+
where(conditionOrField) {
|
|
213
|
+
if (this._model.where.items.length === 0) {
|
|
214
|
+
this._model.where = conditionOrField;
|
|
387
215
|
return this;
|
|
388
216
|
}
|
|
389
217
|
|
|
390
|
-
if (
|
|
391
|
-
|
|
218
|
+
if (conditionOrField.type === "group" && conditionOrField.join === "AND") {
|
|
219
|
+
for (const item of conditionOrField.items) {
|
|
220
|
+
this._model.where.items.push(item);
|
|
221
|
+
}
|
|
222
|
+
return this;
|
|
392
223
|
}
|
|
224
|
+
|
|
225
|
+
this._model.where.items.push(conditionOrField);
|
|
393
226
|
return this;
|
|
394
227
|
}
|
|
395
228
|
|
|
@@ -397,9 +230,9 @@ export class SqlBuilder {
|
|
|
397
230
|
* WHERE 原始片段(不做转义),可附带参数。
|
|
398
231
|
*/
|
|
399
232
|
whereRaw(sql, params) {
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
233
|
+
let paramList = [];
|
|
234
|
+
if (Array.isArray(params)) {
|
|
235
|
+
paramList = params;
|
|
403
236
|
}
|
|
404
237
|
|
|
405
238
|
this._model.where.items.push({ type: "raw", sql: sql, params: paramList });
|
|
@@ -416,43 +249,11 @@ export class SqlBuilder {
|
|
|
416
249
|
|
|
417
250
|
/**
|
|
418
251
|
* ORDER BY
|
|
419
|
-
* @param fields - 格式为 ["field
|
|
252
|
+
* @param fields - 格式为 [{ field: "field", dir: "ASC" }]
|
|
420
253
|
*/
|
|
421
254
|
orderBy(fields) {
|
|
422
255
|
for (const item of fields) {
|
|
423
|
-
|
|
424
|
-
throw new Error(`orderBy 字段必须是 "字段#方向" 格式的字符串(例如:"name#ASC", "id#DESC") (item: ${String(item)})`, {
|
|
425
|
-
cause: null,
|
|
426
|
-
code: "validation"
|
|
427
|
-
});
|
|
428
|
-
}
|
|
429
|
-
|
|
430
|
-
const parts = item.split("#");
|
|
431
|
-
if (parts.length !== 2) {
|
|
432
|
-
throw new Error(`orderBy 字段必须是 "字段#方向" 格式的字符串(例如:"name#ASC", "id#DESC") (item: ${String(item)})`, {
|
|
433
|
-
cause: null,
|
|
434
|
-
code: "validation"
|
|
435
|
-
});
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
const cleanField = parts[0].trim();
|
|
439
|
-
const cleanDir = parts[1].trim().toUpperCase();
|
|
440
|
-
|
|
441
|
-
if (!cleanField) {
|
|
442
|
-
throw new Error(`orderBy 中字段名不能为空 (item: ${item})`, {
|
|
443
|
-
cause: null,
|
|
444
|
-
code: "validation"
|
|
445
|
-
});
|
|
446
|
-
}
|
|
447
|
-
|
|
448
|
-
if (!["ASC", "DESC"].includes(cleanDir)) {
|
|
449
|
-
throw new Error(`ORDER BY 方向必须是 ASC 或 DESC (direction: ${cleanDir})`, {
|
|
450
|
-
cause: null,
|
|
451
|
-
code: "validation"
|
|
452
|
-
});
|
|
453
|
-
}
|
|
454
|
-
|
|
455
|
-
this._model.orderBy.push({ field: cleanField, dir: cleanDir });
|
|
256
|
+
this._model.orderBy.push({ field: item.field, dir: item.dir });
|
|
456
257
|
}
|
|
457
258
|
return this;
|
|
458
259
|
}
|
|
@@ -461,9 +262,8 @@ export class SqlBuilder {
|
|
|
461
262
|
* LIMIT
|
|
462
263
|
*/
|
|
463
264
|
limit(count, offset) {
|
|
464
|
-
|
|
465
|
-
this._model.
|
|
466
|
-
this._model.offset = result.offsetValue;
|
|
265
|
+
this._model.limit = count;
|
|
266
|
+
this._model.offset = offset;
|
|
467
267
|
return this;
|
|
468
268
|
}
|
|
469
269
|
|
|
@@ -471,8 +271,7 @@ export class SqlBuilder {
|
|
|
471
271
|
* OFFSET
|
|
472
272
|
*/
|
|
473
273
|
offset(count) {
|
|
474
|
-
|
|
475
|
-
this._model.offset = offsetValue;
|
|
274
|
+
this._model.offset = count;
|
|
476
275
|
return this;
|
|
477
276
|
}
|
|
478
277
|
|
|
@@ -480,20 +279,23 @@ export class SqlBuilder {
|
|
|
480
279
|
* 构建 SELECT 查询
|
|
481
280
|
*/
|
|
482
281
|
toSelectSql() {
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
282
|
+
let selectSql = "*";
|
|
283
|
+
if (this._model.select.length > 0) {
|
|
284
|
+
selectSql = this._model.select
|
|
285
|
+
.map((item) => {
|
|
286
|
+
if (item.type === "raw") {
|
|
287
|
+
return item.value;
|
|
288
|
+
}
|
|
289
|
+
return escapeField(item.value, this._quoteIdent);
|
|
290
|
+
})
|
|
291
|
+
.join(", ");
|
|
292
|
+
}
|
|
494
293
|
|
|
495
294
|
const params = [];
|
|
496
|
-
|
|
295
|
+
let fromSql = this._model.from.value;
|
|
296
|
+
if (this._model.from.type !== "raw") {
|
|
297
|
+
fromSql = escapeTable(this._model.from.value, this._quoteIdent);
|
|
298
|
+
}
|
|
497
299
|
let sql = `SELECT ${selectSql} FROM ${fromSql}`;
|
|
498
300
|
|
|
499
301
|
if (this._model.joins.length > 0) {
|
|
@@ -514,7 +316,7 @@ export class SqlBuilder {
|
|
|
514
316
|
|
|
515
317
|
if (this._model.limit !== null) {
|
|
516
318
|
sql += ` LIMIT ${this._model.limit}`;
|
|
517
|
-
if (this._model.offset !== null) {
|
|
319
|
+
if (this._model.offset !== null && this._model.offset !== undefined) {
|
|
518
320
|
sql += ` OFFSET ${this._model.offset}`;
|
|
519
321
|
}
|
|
520
322
|
}
|
|
@@ -529,7 +331,12 @@ export class SqlBuilder {
|
|
|
529
331
|
const escapedTable = escapeTable(table, this._quoteIdent);
|
|
530
332
|
|
|
531
333
|
if (Array.isArray(data)) {
|
|
532
|
-
|
|
334
|
+
if (data.length === 0) {
|
|
335
|
+
return { sql: "", params: [] };
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
const firstRow = data[0] || {};
|
|
339
|
+
const fields = Object.keys(firstRow);
|
|
533
340
|
const escapedFields = fields.map((field) => escapeField(field, this._quoteIdent));
|
|
534
341
|
const placeholders = fields.map(() => "?").join(", ");
|
|
535
342
|
const values = data.map(() => `(${placeholders})`).join(", ");
|
|
@@ -537,9 +344,7 @@ export class SqlBuilder {
|
|
|
537
344
|
|
|
538
345
|
for (const row of data) {
|
|
539
346
|
for (const field of fields) {
|
|
540
|
-
|
|
541
|
-
validateParam(value);
|
|
542
|
-
params.push(value);
|
|
347
|
+
params.push(row[field]);
|
|
543
348
|
}
|
|
544
349
|
}
|
|
545
350
|
|
|
@@ -550,16 +355,6 @@ export class SqlBuilder {
|
|
|
550
355
|
}
|
|
551
356
|
|
|
552
357
|
const fields = Object.keys(data);
|
|
553
|
-
if (fields.length === 0) {
|
|
554
|
-
throw new Error(`插入数据必须至少有一个字段 (table: ${table})`, {
|
|
555
|
-
cause: null,
|
|
556
|
-
code: "validation"
|
|
557
|
-
});
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
for (const field of fields) {
|
|
561
|
-
validateParam(data[field]);
|
|
562
|
-
}
|
|
563
358
|
|
|
564
359
|
const escapedFields = fields.map((field) => escapeField(field, this._quoteIdent));
|
|
565
360
|
const placeholders = fields.map(() => "?").join(", ");
|
|
@@ -579,12 +374,6 @@ export class SqlBuilder {
|
|
|
579
374
|
*/
|
|
580
375
|
toUpdateSql(table, data) {
|
|
581
376
|
const fields = Object.keys(data);
|
|
582
|
-
if (fields.length === 0) {
|
|
583
|
-
throw new Error("更新数据必须至少有一个字段", {
|
|
584
|
-
cause: null,
|
|
585
|
-
code: "validation"
|
|
586
|
-
});
|
|
587
|
-
}
|
|
588
377
|
|
|
589
378
|
const params = [];
|
|
590
379
|
for (const value of Object.values(data)) {
|
|
@@ -618,7 +407,10 @@ export class SqlBuilder {
|
|
|
618
407
|
*/
|
|
619
408
|
toCountSql() {
|
|
620
409
|
const params = [];
|
|
621
|
-
|
|
410
|
+
let fromSql = this._model.from.value;
|
|
411
|
+
if (this._model.from.type !== "raw") {
|
|
412
|
+
fromSql = escapeTable(this._model.from.value, this._quoteIdent);
|
|
413
|
+
}
|
|
622
414
|
let sql = `SELECT COUNT(*) as total FROM ${fromSql}`;
|
|
623
415
|
|
|
624
416
|
if (this._model.joins.length > 0) {
|
|
@@ -676,7 +468,6 @@ export class SqlBuilder {
|
|
|
676
468
|
whenList.push("WHEN ? THEN ?");
|
|
677
469
|
args.push(row.id);
|
|
678
470
|
const value = row.data[field];
|
|
679
|
-
assertNoUndefinedParam(value, "SQL 参数值");
|
|
680
471
|
args.push(value);
|
|
681
472
|
}
|
|
682
473
|
|
|
@@ -705,10 +496,3 @@ export class SqlBuilder {
|
|
|
705
496
|
return { sql: sql, params: args };
|
|
706
497
|
}
|
|
707
498
|
}
|
|
708
|
-
|
|
709
|
-
/**
|
|
710
|
-
* 创建新的 SQL 构建器实例
|
|
711
|
-
*/
|
|
712
|
-
export function createQueryBuilder() {
|
|
713
|
-
return new SqlBuilder();
|
|
714
|
-
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "befly",
|
|
3
|
-
"version": "3.
|
|
4
|
-
"gitHead": "
|
|
3
|
+
"version": "3.22.1",
|
|
4
|
+
"gitHead": "fdeb630232962a7f6d023d368f90fd9a91ea20a0",
|
|
5
5
|
"private": false,
|
|
6
6
|
"description": "Befly - 为 Bun 专属打造的 JavaScript API 接口框架核心引擎",
|
|
7
7
|
"keywords": [
|
package/paths.js
CHANGED
|
@@ -112,9 +112,9 @@ export const appApiDir = join(appDir, "apis");
|
|
|
112
112
|
export const appTableDir = join(appDir, "tables");
|
|
113
113
|
|
|
114
114
|
/**
|
|
115
|
-
*
|
|
115
|
+
* 项目上传/公共文件目录
|
|
116
116
|
* @description 默认 {appDir}/public,可通过 config.publicDir 覆盖
|
|
117
|
-
* @usage
|
|
117
|
+
* @usage 用于本地上传文件保存目录解析,不承担 HTTP 静态托管职责
|
|
118
118
|
*/
|
|
119
119
|
export function getAppPublicDir(publicDir = "./public") {
|
|
120
120
|
if (isAbsolute(publicDir)) {
|
package/router/static.js
CHANGED
|
@@ -20,7 +20,8 @@ export function staticHandler(corsConfig = undefined, publicDir = "./public") {
|
|
|
20
20
|
const corsHeaders = setCorsOptions(req, corsConfig);
|
|
21
21
|
|
|
22
22
|
const url = new URL(req.url);
|
|
23
|
-
const
|
|
23
|
+
const publicPath = url.pathname.replace(/^\/public/, "") || "/";
|
|
24
|
+
const filePath = join(getAppPublicDir(publicDir), publicPath);
|
|
24
25
|
|
|
25
26
|
try {
|
|
26
27
|
// OPTIONS预检请求
|
package/sql/befly.sql
CHANGED
|
@@ -79,6 +79,26 @@ CREATE TABLE IF NOT EXISTS `befly_email_log` (
|
|
|
79
79
|
PRIMARY KEY (`id`)
|
|
80
80
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
|
81
81
|
|
|
82
|
+
CREATE TABLE IF NOT EXISTS `befly_file` (
|
|
83
|
+
`id` BIGINT NOT NULL,
|
|
84
|
+
`user_id` BIGINT NOT NULL DEFAULT 0,
|
|
85
|
+
`file_path` VARCHAR(500) NOT NULL DEFAULT '',
|
|
86
|
+
`url` VARCHAR(1000) NOT NULL DEFAULT '',
|
|
87
|
+
`is_image` TINYINT NOT NULL DEFAULT 0,
|
|
88
|
+
`file_type` VARCHAR(20) NOT NULL DEFAULT '',
|
|
89
|
+
`file_size` BIGINT NOT NULL DEFAULT 0,
|
|
90
|
+
`file_key` VARCHAR(100) NOT NULL DEFAULT '',
|
|
91
|
+
`file_ext` VARCHAR(20) NOT NULL DEFAULT '',
|
|
92
|
+
`file_name` VARCHAR(200) NOT NULL DEFAULT '',
|
|
93
|
+
`state` TINYINT NOT NULL DEFAULT 1,
|
|
94
|
+
`created_at` BIGINT NOT NULL DEFAULT 0,
|
|
95
|
+
`updated_at` BIGINT NOT NULL DEFAULT 0,
|
|
96
|
+
`deleted_at` BIGINT NULL DEFAULT NULL,
|
|
97
|
+
PRIMARY KEY (`id`),
|
|
98
|
+
KEY `idx_befly_file_user_id` (`user_id`),
|
|
99
|
+
KEY `idx_befly_file_is_image` (`is_image`)
|
|
100
|
+
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
|
|
101
|
+
|
|
82
102
|
CREATE TABLE IF NOT EXISTS `befly_login_log` (
|
|
83
103
|
`id` BIGINT NOT NULL,
|
|
84
104
|
`admin_id` BIGINT NOT NULL DEFAULT 0,
|