befly 3.9.37 → 3.9.39
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +38 -39
- package/befly.config.ts +62 -40
- package/checks/checkApi.ts +16 -16
- package/checks/checkApp.ts +19 -25
- package/checks/checkTable.ts +42 -42
- package/docs/README.md +42 -35
- package/docs/{api.md → api/api.md} +225 -235
- package/docs/cipher.md +71 -69
- package/docs/database.md +155 -153
- package/docs/{examples.md → guide/examples.md} +181 -181
- package/docs/guide/quickstart.md +331 -0
- package/docs/hooks/auth.md +38 -0
- package/docs/hooks/cors.md +28 -0
- package/docs/{hook.md → hooks/hook.md} +140 -57
- package/docs/hooks/parser.md +19 -0
- package/docs/hooks/rateLimit.md +47 -0
- package/docs/{redis.md → infra/redis.md} +84 -93
- package/docs/plugins/cipher.md +61 -0
- package/docs/plugins/database.md +128 -0
- package/docs/{plugin.md → plugins/plugin.md} +83 -81
- package/docs/quickstart.md +26 -26
- package/docs/{addon.md → reference/addon.md} +46 -46
- package/docs/{config.md → reference/config.md} +32 -80
- package/docs/{logger.md → reference/logger.md} +52 -52
- package/docs/{sync.md → reference/sync.md} +32 -35
- package/docs/{table.md → reference/table.md} +7 -7
- package/docs/{validator.md → reference/validator.md} +57 -57
- package/hooks/auth.ts +8 -4
- package/hooks/cors.ts +13 -13
- package/hooks/parser.ts +37 -17
- package/hooks/permission.ts +26 -14
- package/hooks/rateLimit.ts +276 -0
- package/hooks/validator.ts +15 -7
- package/lib/asyncContext.ts +43 -0
- package/lib/cacheHelper.ts +212 -81
- package/lib/cacheKeys.ts +38 -0
- package/lib/cipher.ts +30 -30
- package/lib/connect.ts +28 -28
- package/lib/dbHelper.ts +211 -109
- package/lib/jwt.ts +16 -16
- package/lib/logger.ts +610 -19
- package/lib/redisHelper.ts +185 -44
- package/lib/sqlBuilder.ts +90 -91
- package/lib/validator.ts +59 -39
- package/loader/loadApis.ts +53 -47
- package/loader/loadHooks.ts +40 -14
- package/loader/loadPlugins.ts +16 -17
- package/main.ts +57 -47
- package/package.json +47 -45
- package/paths.ts +15 -14
- package/plugins/cache.ts +5 -4
- package/plugins/cipher.ts +3 -3
- package/plugins/config.ts +2 -2
- package/plugins/db.ts +9 -9
- package/plugins/jwt.ts +3 -3
- package/plugins/logger.ts +8 -12
- package/plugins/redis.ts +8 -8
- package/plugins/tool.ts +6 -6
- package/router/api.ts +85 -56
- package/router/static.ts +12 -12
- package/sync/syncAll.ts +12 -12
- package/sync/syncApi.ts +55 -54
- package/sync/syncDb/apply.ts +20 -19
- package/sync/syncDb/constants.ts +25 -23
- package/sync/syncDb/ddl.ts +35 -36
- package/sync/syncDb/helpers.ts +6 -9
- package/sync/syncDb/schema.ts +10 -9
- package/sync/syncDb/sqlite.ts +7 -8
- package/sync/syncDb/table.ts +37 -35
- package/sync/syncDb/tableCreate.ts +21 -20
- package/sync/syncDb/types.ts +23 -20
- package/sync/syncDb/version.ts +10 -10
- package/sync/syncDb.ts +43 -36
- package/sync/syncDev.ts +74 -66
- package/sync/syncMenu.ts +190 -57
- package/tests/api-integration-array-number.test.ts +282 -0
- package/tests/befly-config-env.test.ts +78 -0
- package/tests/cacheHelper.test.ts +135 -104
- package/tests/cacheKeys.test.ts +41 -0
- package/tests/cipher.test.ts +90 -89
- package/tests/dbHelper-advanced.test.ts +140 -134
- package/tests/dbHelper-all-array-types.test.ts +316 -0
- package/tests/dbHelper-array-serialization.test.ts +258 -0
- package/tests/dbHelper-columns.test.ts +56 -55
- package/tests/dbHelper-execute.test.ts +45 -44
- package/tests/dbHelper-joins.test.ts +124 -119
- package/tests/fields-redis-cache.test.ts +29 -27
- package/tests/fields-validate.test.ts +38 -38
- package/tests/getClientIp.test.ts +54 -0
- package/tests/integration.test.ts +69 -67
- package/tests/jwt.test.ts +27 -26
- package/tests/logger.test.ts +267 -34
- package/tests/rateLimit-hook.test.ts +477 -0
- package/tests/redisHelper.test.ts +187 -188
- package/tests/redisKeys.test.ts +6 -73
- package/tests/scanConfig.test.ts +144 -0
- package/tests/sqlBuilder-advanced.test.ts +217 -215
- package/tests/sqlBuilder.test.ts +92 -91
- package/tests/sync-connection.test.ts +29 -29
- package/tests/syncDb-apply.test.ts +97 -96
- package/tests/syncDb-array-number.test.ts +160 -0
- package/tests/syncDb-constants.test.ts +48 -47
- package/tests/syncDb-ddl.test.ts +99 -98
- package/tests/syncDb-helpers.test.ts +29 -28
- package/tests/syncDb-schema.test.ts +61 -60
- package/tests/syncDb-types.test.ts +60 -59
- package/tests/syncMenu-paths.test.ts +68 -0
- package/tests/util.test.ts +42 -41
- package/tests/validator-array-number.test.ts +310 -0
- package/tests/validator-default.test.ts +373 -0
- package/tests/validator.test.ts +271 -266
- package/tsconfig.json +4 -5
- package/types/api.d.ts +7 -12
- package/types/befly.d.ts +60 -13
- package/types/cache.d.ts +8 -4
- package/types/common.d.ts +17 -9
- package/types/context.d.ts +2 -2
- package/types/crypto.d.ts +23 -0
- package/types/database.d.ts +19 -19
- package/types/hook.d.ts +2 -2
- package/types/jwt.d.ts +118 -0
- package/types/logger.d.ts +30 -0
- package/types/plugin.d.ts +4 -4
- package/types/redis.d.ts +7 -3
- package/types/roleApisCache.ts +23 -0
- package/types/sync.d.ts +10 -10
- package/types/table.d.ts +50 -9
- package/types/validate.d.ts +69 -0
- package/utils/addonHelper.ts +90 -0
- package/utils/arrayKeysToCamel.ts +18 -0
- package/utils/calcPerfTime.ts +13 -0
- package/utils/configTypes.ts +3 -0
- package/utils/cors.ts +19 -0
- package/utils/fieldClear.ts +75 -0
- package/utils/genShortId.ts +12 -0
- package/utils/getClientIp.ts +45 -0
- package/utils/keysToCamel.ts +22 -0
- package/utils/keysToSnake.ts +22 -0
- package/utils/modules.ts +98 -0
- package/utils/pickFields.ts +19 -0
- package/utils/process.ts +56 -0
- package/utils/regex.ts +225 -0
- package/utils/response.ts +115 -0
- package/utils/route.ts +23 -0
- package/utils/scanConfig.ts +142 -0
- package/utils/scanFiles.ts +48 -0
- package/.prettierignore +0 -2
- package/.prettierrc +0 -12
- package/docs/1-/345/237/272/346/234/254/344/273/213/347/273/215.md +0 -35
- package/docs/2-/345/210/235/346/255/245/344/275/223/351/252/214.md +0 -64
- package/docs/3-/347/254/254/344/270/200/344/270/252/346/216/245/345/217/243.md +0 -46
- package/docs/4-/346/223/215/344/275/234/346/225/260/346/215/256/345/272/223.md +0 -172
- package/hooks/requestLogger.ts +0 -84
- package/types/index.ts +0 -24
- package/util.ts +0 -283
|
@@ -3,135 +3,136 @@
|
|
|
3
3
|
* 测试复杂 WHERE 条件、边界条件、SQL 注入防护
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
import { describe, test, expect } from
|
|
7
|
-
import { SqlBuilder } from '../lib/sqlBuilder';
|
|
6
|
+
import { describe, test, expect } from "bun:test";
|
|
8
7
|
|
|
9
|
-
|
|
10
|
-
|
|
8
|
+
import { SqlBuilder } from "../lib/sqlBuilder";
|
|
9
|
+
|
|
10
|
+
describe("SqlBuilder - WHERE 操作符完整测试", () => {
|
|
11
|
+
test("$in - 空数组应抛出错误", () => {
|
|
11
12
|
// **已修复**:$in 传入空数组 [] 会抛出错误
|
|
12
13
|
// 空数组会导致查询永远不匹配任何记录
|
|
13
14
|
|
|
14
15
|
const builder = new SqlBuilder();
|
|
15
16
|
|
|
16
17
|
expect(() => {
|
|
17
|
-
builder.select([
|
|
18
|
-
}).toThrow(
|
|
18
|
+
builder.select(["*"]).from("users").where({ id$in: [] }).toSelectSql();
|
|
19
|
+
}).toThrow("$in 操作符的数组不能为空");
|
|
19
20
|
});
|
|
20
21
|
|
|
21
|
-
test(
|
|
22
|
+
test("$in - 单个值应生成正确的 SQL", () => {
|
|
22
23
|
const builder = new SqlBuilder();
|
|
23
24
|
const result = builder
|
|
24
|
-
.select([
|
|
25
|
-
.from(
|
|
25
|
+
.select(["*"])
|
|
26
|
+
.from("users")
|
|
26
27
|
.where({ id$in: [1] })
|
|
27
28
|
.toSelectSql();
|
|
28
29
|
|
|
29
|
-
expect(result.sql).toContain(
|
|
30
|
+
expect(result.sql).toContain("IN (?)");
|
|
30
31
|
expect(result.params).toEqual([1]);
|
|
31
32
|
});
|
|
32
33
|
|
|
33
|
-
test(
|
|
34
|
+
test("$in - 多个值应生成多个占位符", () => {
|
|
34
35
|
const builder = new SqlBuilder();
|
|
35
36
|
const result = builder
|
|
36
|
-
.select([
|
|
37
|
-
.from(
|
|
37
|
+
.select(["*"])
|
|
38
|
+
.from("users")
|
|
38
39
|
.where({ id$in: [1, 2, 3, 4, 5] })
|
|
39
40
|
.toSelectSql();
|
|
40
41
|
|
|
41
|
-
expect(result.sql).toContain(
|
|
42
|
+
expect(result.sql).toContain("IN (?,?,?,?,?)");
|
|
42
43
|
expect(result.params).toEqual([1, 2, 3, 4, 5]);
|
|
43
44
|
});
|
|
44
45
|
|
|
45
|
-
test(
|
|
46
|
+
test("$notIn - 应生成 NOT IN 语句", () => {
|
|
46
47
|
const builder = new SqlBuilder();
|
|
47
48
|
const result = builder
|
|
48
|
-
.select([
|
|
49
|
-
.from(
|
|
50
|
-
.where({ status$notIn: [
|
|
49
|
+
.select(["*"])
|
|
50
|
+
.from("users")
|
|
51
|
+
.where({ status$notIn: ["banned", "deleted"] })
|
|
51
52
|
.toSelectSql();
|
|
52
53
|
|
|
53
|
-
expect(result.sql).toContain(
|
|
54
|
-
expect(result.params).toEqual([
|
|
54
|
+
expect(result.sql).toContain("NOT IN");
|
|
55
|
+
expect(result.params).toEqual(["banned", "deleted"]);
|
|
55
56
|
});
|
|
56
57
|
|
|
57
|
-
test(
|
|
58
|
+
test("$between - 应生成 BETWEEN AND 语句", () => {
|
|
58
59
|
const builder = new SqlBuilder();
|
|
59
60
|
const result = builder
|
|
60
|
-
.select([
|
|
61
|
-
.from(
|
|
61
|
+
.select(["*"])
|
|
62
|
+
.from("users")
|
|
62
63
|
.where({ age$between: [18, 65] })
|
|
63
64
|
.toSelectSql();
|
|
64
65
|
|
|
65
|
-
expect(result.sql).toContain(
|
|
66
|
+
expect(result.sql).toContain("BETWEEN ? AND ?");
|
|
66
67
|
expect(result.params).toEqual([18, 65]);
|
|
67
68
|
});
|
|
68
69
|
|
|
69
|
-
test(
|
|
70
|
+
test("$between - 非数组值应如何处理", () => {
|
|
70
71
|
// **问题**:$between 应该接收 [min, max]
|
|
71
72
|
// 如果传入非数组,应该报错还是忽略?
|
|
72
73
|
|
|
73
74
|
const builder = new SqlBuilder();
|
|
74
75
|
const result = builder
|
|
75
|
-
.select([
|
|
76
|
-
.from(
|
|
76
|
+
.select(["*"])
|
|
77
|
+
.from("users")
|
|
77
78
|
.where({ age$between: 18 as any })
|
|
78
79
|
.toSelectSql();
|
|
79
80
|
|
|
80
|
-
//
|
|
81
|
-
|
|
81
|
+
// 当前实现:非数组不会生成 BETWEEN 条件(保持为其它 where 逻辑输出)
|
|
82
|
+
expect(typeof result.sql).toBe("string");
|
|
82
83
|
});
|
|
83
84
|
|
|
84
|
-
test(
|
|
85
|
+
test("$between - 数组长度不为 2 应如何处理", () => {
|
|
85
86
|
const builder = new SqlBuilder();
|
|
86
87
|
const result = builder
|
|
87
|
-
.select([
|
|
88
|
-
.from(
|
|
88
|
+
.select(["*"])
|
|
89
|
+
.from("users")
|
|
89
90
|
.where({ age$between: [18] as any })
|
|
90
91
|
.toSelectSql();
|
|
91
92
|
|
|
92
|
-
//
|
|
93
|
-
|
|
93
|
+
// **建议**:未来可验证数组长度必须为 2
|
|
94
|
+
expect(typeof result.sql).toBe("string");
|
|
94
95
|
});
|
|
95
96
|
|
|
96
|
-
test(
|
|
97
|
+
test("$null - 应生成 IS NULL 语句", () => {
|
|
97
98
|
const builder = new SqlBuilder();
|
|
98
|
-
const result = builder.select([
|
|
99
|
+
const result = builder.select(["*"]).from("users").where({ deletedAt$null: true }).toSelectSql();
|
|
99
100
|
|
|
100
|
-
expect(result.sql).toContain(
|
|
101
|
+
expect(result.sql).toContain("IS NULL");
|
|
101
102
|
expect(result.params.length).toBe(0); // IS NULL 不需要参数
|
|
102
103
|
});
|
|
103
104
|
|
|
104
|
-
test(
|
|
105
|
+
test("$notNull - 应生成 IS NOT NULL 语句", () => {
|
|
105
106
|
const builder = new SqlBuilder();
|
|
106
|
-
const result = builder.select([
|
|
107
|
+
const result = builder.select(["*"]).from("users").where({ email$notNull: true }).toSelectSql();
|
|
107
108
|
|
|
108
|
-
expect(result.sql).toContain(
|
|
109
|
+
expect(result.sql).toContain("IS NOT NULL");
|
|
109
110
|
});
|
|
110
111
|
|
|
111
|
-
test(
|
|
112
|
+
test("$like - 应生成 LIKE 语句", () => {
|
|
112
113
|
const builder = new SqlBuilder();
|
|
113
|
-
const result = builder.select([
|
|
114
|
+
const result = builder.select(["*"]).from("users").where({ name$like: "%john%" }).toSelectSql();
|
|
114
115
|
|
|
115
|
-
expect(result.sql).toContain(
|
|
116
|
-
expect(result.params).toEqual([
|
|
116
|
+
expect(result.sql).toContain("LIKE ?");
|
|
117
|
+
expect(result.params).toEqual(["%john%"]);
|
|
117
118
|
});
|
|
118
119
|
|
|
119
|
-
test(
|
|
120
|
+
test("$like - 通配符应由用户控制", () => {
|
|
120
121
|
// 用户应自己添加 %
|
|
121
122
|
const builder = new SqlBuilder();
|
|
122
|
-
const result = builder.select([
|
|
123
|
+
const result = builder.select(["*"]).from("users").where({ name$like: "john" }).toSelectSql();
|
|
123
124
|
|
|
124
125
|
// 不应自动添加 %
|
|
125
|
-
expect(result.params).toEqual([
|
|
126
|
+
expect(result.params).toEqual(["john"]);
|
|
126
127
|
});
|
|
127
128
|
|
|
128
|
-
test(
|
|
129
|
+
test("比较操作符 - 应生成正确的符号", () => {
|
|
129
130
|
const cases = [
|
|
130
|
-
{ op:
|
|
131
|
-
{ op:
|
|
132
|
-
{ op:
|
|
133
|
-
{ op:
|
|
134
|
-
{ op:
|
|
131
|
+
{ op: "gt", symbol: ">", value: 18 },
|
|
132
|
+
{ op: "gte", symbol: ">=", value: 18 },
|
|
133
|
+
{ op: "lt", symbol: "<", value: 65 },
|
|
134
|
+
{ op: "lte", symbol: "<=", value: 65 },
|
|
135
|
+
{ op: "ne", symbol: "!=", value: 0 }
|
|
135
136
|
];
|
|
136
137
|
|
|
137
138
|
cases.forEach(({ op, symbol, value }) => {
|
|
@@ -139,7 +140,7 @@ describe('SqlBuilder - WHERE 操作符完整测试', () => {
|
|
|
139
140
|
const where: any = {};
|
|
140
141
|
where[`age$${op}`] = value;
|
|
141
142
|
|
|
142
|
-
const result = builder.select([
|
|
143
|
+
const result = builder.select(["*"]).from("users").where(where).toSelectSql();
|
|
143
144
|
|
|
144
145
|
expect(result.sql).toContain(symbol);
|
|
145
146
|
expect(result.params).toEqual([value]);
|
|
@@ -147,82 +148,82 @@ describe('SqlBuilder - WHERE 操作符完整测试', () => {
|
|
|
147
148
|
});
|
|
148
149
|
});
|
|
149
150
|
|
|
150
|
-
describe(
|
|
151
|
-
test(
|
|
151
|
+
describe("SqlBuilder - 复杂 WHERE 条件", () => {
|
|
152
|
+
test("$or - 单个条件应正确处理", () => {
|
|
152
153
|
const builder = new SqlBuilder();
|
|
153
154
|
const result = builder
|
|
154
|
-
.select([
|
|
155
|
-
.from(
|
|
155
|
+
.select(["*"])
|
|
156
|
+
.from("users")
|
|
156
157
|
.where({
|
|
157
|
-
$or: [{ name:
|
|
158
|
+
$or: [{ name: "john" }]
|
|
158
159
|
})
|
|
159
160
|
.toSelectSql();
|
|
160
161
|
|
|
161
162
|
// 单个条件不应该显示OR关键字
|
|
162
|
-
expect(result.sql).not.toContain(
|
|
163
|
-
expect(result.params).toEqual([
|
|
163
|
+
expect(result.sql).not.toContain(" OR ");
|
|
164
|
+
expect(result.params).toEqual(["john"]);
|
|
164
165
|
});
|
|
165
166
|
|
|
166
|
-
test(
|
|
167
|
+
test("$or - 多个条件应用 OR 连接", () => {
|
|
167
168
|
const builder = new SqlBuilder();
|
|
168
169
|
const result = builder
|
|
169
|
-
.select([
|
|
170
|
-
.from(
|
|
170
|
+
.select(["*"])
|
|
171
|
+
.from("users")
|
|
171
172
|
.where({
|
|
172
|
-
$or: [{ name:
|
|
173
|
+
$or: [{ name: "john" }, { email: "john@example.com" }]
|
|
173
174
|
})
|
|
174
175
|
.toSelectSql();
|
|
175
176
|
|
|
176
|
-
expect(result.sql).toContain(
|
|
177
|
-
expect(result.params).toEqual([
|
|
177
|
+
expect(result.sql).toContain("OR");
|
|
178
|
+
expect(result.params).toEqual(["john", "john@example.com"]);
|
|
178
179
|
});
|
|
179
180
|
|
|
180
|
-
test(
|
|
181
|
+
test("$or - 空数组应如何处理", () => {
|
|
181
182
|
const builder = new SqlBuilder();
|
|
182
|
-
const result = builder.select([
|
|
183
|
+
const result = builder.select(["*"]).from("users").where({ $or: [] }).toSelectSql();
|
|
183
184
|
|
|
184
185
|
// **问题**:空 $or 应该跳过还是报错?
|
|
185
|
-
console.log(
|
|
186
|
+
console.log("$or 空数组生成的 SQL:", result.sql);
|
|
186
187
|
});
|
|
187
188
|
|
|
188
|
-
test(
|
|
189
|
+
test("$and + $or 嵌套", () => {
|
|
189
190
|
const builder = new SqlBuilder();
|
|
190
191
|
const result = builder
|
|
191
|
-
.select([
|
|
192
|
-
.from(
|
|
192
|
+
.select(["*"])
|
|
193
|
+
.from("users")
|
|
193
194
|
.where({
|
|
194
195
|
age$gte: 18,
|
|
195
|
-
$or: [{ role:
|
|
196
|
+
$or: [{ role: "admin" }, { role: "moderator" }]
|
|
196
197
|
})
|
|
197
198
|
.toSelectSql();
|
|
198
199
|
|
|
199
|
-
expect(result.sql).toContain(
|
|
200
|
-
expect(result.sql).toContain(
|
|
201
|
-
expect(result.params).toEqual([18,
|
|
200
|
+
expect(result.sql).toContain("AND");
|
|
201
|
+
expect(result.sql).toContain("OR");
|
|
202
|
+
expect(result.params).toEqual([18, "admin", "moderator"]);
|
|
202
203
|
});
|
|
203
204
|
|
|
204
|
-
test(
|
|
205
|
+
test("$or 内嵌套多个操作符", () => {
|
|
205
206
|
const builder = new SqlBuilder();
|
|
206
207
|
const result = builder
|
|
207
|
-
.select([
|
|
208
|
-
.from(
|
|
208
|
+
.select(["*"])
|
|
209
|
+
.from("users")
|
|
209
210
|
.where({
|
|
210
211
|
$or: [{ age$gt: 60 }, { age$lt: 18 }]
|
|
211
212
|
})
|
|
212
213
|
.toSelectSql();
|
|
213
214
|
|
|
214
|
-
expect(result.sql).toContain(
|
|
215
|
-
expect(result.sql).toContain(
|
|
216
|
-
expect(result.sql).toContain(
|
|
215
|
+
expect(result.sql).toContain("OR");
|
|
216
|
+
expect(result.sql).toContain(">");
|
|
217
|
+
expect(result.sql).toContain("<");
|
|
217
218
|
});
|
|
218
219
|
|
|
219
|
-
test(
|
|
220
|
+
test("深层嵌套的 $or 和 $and", () => {
|
|
220
221
|
const builder = new SqlBuilder();
|
|
221
222
|
const result = builder
|
|
222
|
-
.select([
|
|
223
|
-
.from(
|
|
223
|
+
.select(["*"])
|
|
224
|
+
.from("users")
|
|
224
225
|
.where({
|
|
225
|
-
status:
|
|
226
|
+
status: "active",
|
|
226
227
|
$or: [
|
|
227
228
|
{
|
|
228
229
|
$and: [{ age$gte: 18 }, { age$lte: 65 }]
|
|
@@ -233,241 +234,242 @@ describe('SqlBuilder - 复杂 WHERE 条件', () => {
|
|
|
233
234
|
.toSelectSql();
|
|
234
235
|
|
|
235
236
|
expect(result.sql).toBeDefined();
|
|
236
|
-
console.log(
|
|
237
|
+
console.log("深层嵌套生成的 SQL:", result.sql);
|
|
237
238
|
});
|
|
238
239
|
});
|
|
239
240
|
|
|
240
|
-
describe(
|
|
241
|
-
test(
|
|
241
|
+
describe("SqlBuilder - 字段名转义", () => {
|
|
242
|
+
test("普通字段名应添加反引号", () => {
|
|
242
243
|
const builder = new SqlBuilder();
|
|
243
|
-
const result = builder.select([
|
|
244
|
+
const result = builder.select(["name", "email"]).from("users").toSelectSql();
|
|
244
245
|
|
|
245
|
-
expect(result.sql).toContain(
|
|
246
|
-
expect(result.sql).toContain(
|
|
246
|
+
expect(result.sql).toContain("`name`");
|
|
247
|
+
expect(result.sql).toContain("`email`");
|
|
247
248
|
});
|
|
248
249
|
|
|
249
|
-
test(
|
|
250
|
+
test("星号不应添加反引号", () => {
|
|
250
251
|
const builder = new SqlBuilder();
|
|
251
|
-
const result = builder.select([
|
|
252
|
+
const result = builder.select(["*"]).from("users").toSelectSql();
|
|
252
253
|
|
|
253
|
-
expect(result.sql).toContain(
|
|
254
|
-
expect(result.sql).not.toContain(
|
|
254
|
+
expect(result.sql).toContain("SELECT *");
|
|
255
|
+
expect(result.sql).not.toContain("`*`");
|
|
255
256
|
});
|
|
256
257
|
|
|
257
|
-
test(
|
|
258
|
+
test("已有反引号的字段不应重复添加", () => {
|
|
258
259
|
const builder = new SqlBuilder();
|
|
259
|
-
const result = builder.select([
|
|
260
|
+
const result = builder.select(["`user_id`", "`user_name`"]).from("users").toSelectSql();
|
|
260
261
|
|
|
261
262
|
// 不应变成 `\`user_id\``
|
|
262
|
-
expect(result.sql).toContain(
|
|
263
|
-
expect(result.sql).not.toContain(
|
|
263
|
+
expect(result.sql).toContain("`user_id`");
|
|
264
|
+
expect(result.sql).not.toContain("``");
|
|
264
265
|
});
|
|
265
266
|
|
|
266
|
-
test(
|
|
267
|
+
test("表名.字段名应分别转义", () => {
|
|
267
268
|
const builder = new SqlBuilder();
|
|
268
|
-
const result = builder.select([
|
|
269
|
+
const result = builder.select(["users.id", "profiles.bio"]).from("users").toSelectSql();
|
|
269
270
|
|
|
270
|
-
expect(result.sql).toContain(
|
|
271
|
-
expect(result.sql).toContain(
|
|
271
|
+
expect(result.sql).toContain("`users`.`id`");
|
|
272
|
+
expect(result.sql).toContain("`profiles`.`bio`");
|
|
272
273
|
});
|
|
273
274
|
|
|
274
|
-
test(
|
|
275
|
+
test("函数调用不应转义", () => {
|
|
275
276
|
const builder = new SqlBuilder();
|
|
276
|
-
const result = builder.select([
|
|
277
|
+
const result = builder.select(["COUNT(*)", "MAX(age)"]).from("users").toSelectSql();
|
|
277
278
|
|
|
278
|
-
expect(result.sql).toContain(
|
|
279
|
-
expect(result.sql).toContain(
|
|
280
|
-
expect(result.sql).not.toContain(
|
|
279
|
+
expect(result.sql).toContain("COUNT(*)");
|
|
280
|
+
expect(result.sql).toContain("MAX(age)");
|
|
281
|
+
expect(result.sql).not.toContain("`COUNT(*)`");
|
|
281
282
|
});
|
|
282
283
|
|
|
283
|
-
test(
|
|
284
|
+
test("AS 别名应正确处理", () => {
|
|
284
285
|
const builder = new SqlBuilder();
|
|
285
|
-
const result = builder.select([
|
|
286
|
+
const result = builder.select(["name AS userName", "COUNT(*) AS total"]).from("users").toSelectSql();
|
|
286
287
|
|
|
287
|
-
expect(result.sql).toContain(
|
|
288
|
-
expect(result.sql).toContain(
|
|
288
|
+
expect(result.sql).toContain("`name` AS userName");
|
|
289
|
+
expect(result.sql).toContain("COUNT(*) AS total");
|
|
289
290
|
});
|
|
290
291
|
|
|
291
|
-
test(
|
|
292
|
+
test("特殊字符字段名应转义", () => {
|
|
292
293
|
// 包含空格、横杠等特殊字符的字段名
|
|
293
294
|
const builder = new SqlBuilder();
|
|
294
|
-
const result = builder.select([
|
|
295
|
+
const result = builder.select(["user-name", "created at"]).from("users").toSelectSql();
|
|
295
296
|
|
|
296
297
|
// 应该添加反引号保护
|
|
297
|
-
expect(result.sql).toContain(
|
|
298
|
-
expect(result.sql).toContain(
|
|
298
|
+
expect(result.sql).toContain("`user-name`");
|
|
299
|
+
expect(result.sql).toContain("`created at`");
|
|
299
300
|
});
|
|
300
301
|
});
|
|
301
302
|
|
|
302
|
-
describe(
|
|
303
|
-
test(
|
|
303
|
+
describe("SqlBuilder - 表名转义", () => {
|
|
304
|
+
test("普通表名应添加反引号", () => {
|
|
304
305
|
const builder = new SqlBuilder();
|
|
305
|
-
const result = builder.select([
|
|
306
|
+
const result = builder.select(["*"]).from("users").toSelectSql();
|
|
306
307
|
|
|
307
|
-
expect(result.sql).toContain(
|
|
308
|
+
expect(result.sql).toContain("FROM `users`");
|
|
308
309
|
});
|
|
309
310
|
|
|
310
|
-
test(
|
|
311
|
+
test("表别名应正确处理", () => {
|
|
311
312
|
const builder = new SqlBuilder();
|
|
312
|
-
const result = builder.select([
|
|
313
|
+
const result = builder.select(["*"]).from("users u").toSelectSql();
|
|
313
314
|
|
|
314
|
-
expect(result.sql).toContain(
|
|
315
|
+
expect(result.sql).toContain("FROM `users` u");
|
|
315
316
|
});
|
|
316
317
|
|
|
317
|
-
test(
|
|
318
|
+
test("已有反引号的表名不应重复添加", () => {
|
|
318
319
|
const builder = new SqlBuilder();
|
|
319
|
-
const result = builder.select([
|
|
320
|
+
const result = builder.select(["*"]).from("`user_profiles`").toSelectSql();
|
|
320
321
|
|
|
321
|
-
expect(result.sql).toContain(
|
|
322
|
-
expect(result.sql).not.toContain(
|
|
322
|
+
expect(result.sql).toContain("FROM `user_profiles`");
|
|
323
|
+
expect(result.sql).not.toContain("``");
|
|
323
324
|
});
|
|
324
325
|
});
|
|
325
326
|
|
|
326
|
-
describe(
|
|
327
|
-
test(
|
|
327
|
+
describe("SqlBuilder - ORDER BY", () => {
|
|
328
|
+
test("单字段升序排序", () => {
|
|
328
329
|
const builder = new SqlBuilder();
|
|
329
|
-
const result = builder.select([
|
|
330
|
+
const result = builder.select(["*"]).from("users").orderBy(["createdAt#ASC"]).toSelectSql();
|
|
330
331
|
|
|
331
332
|
// ORDER BY目前不转换驼峰,保持原样
|
|
332
|
-
expect(result.sql).toContain(
|
|
333
|
+
expect(result.sql).toContain("ORDER BY `createdAt` ASC");
|
|
333
334
|
});
|
|
334
335
|
|
|
335
|
-
test(
|
|
336
|
+
test("单字段降序排序", () => {
|
|
336
337
|
const builder = new SqlBuilder();
|
|
337
|
-
const result = builder.select([
|
|
338
|
+
const result = builder.select(["*"]).from("users").orderBy(["createdAt#DESC"]).toSelectSql();
|
|
338
339
|
|
|
339
340
|
// ORDER BY目前不转换驼峰,保持原样
|
|
340
|
-
expect(result.sql).toContain(
|
|
341
|
+
expect(result.sql).toContain("ORDER BY `createdAt` DESC");
|
|
341
342
|
});
|
|
342
343
|
|
|
343
|
-
test(
|
|
344
|
+
test("多字段排序", () => {
|
|
344
345
|
const builder = new SqlBuilder();
|
|
345
|
-
const result = builder.select([
|
|
346
|
+
const result = builder.select(["*"]).from("users").orderBy(["priority#DESC", "createdAt#ASC"]).toSelectSql();
|
|
346
347
|
|
|
347
|
-
expect(result.sql).toContain(
|
|
348
|
-
expect(result.sql).toContain(
|
|
349
|
-
expect(result.sql).toContain(
|
|
348
|
+
expect(result.sql).toContain("ORDER BY");
|
|
349
|
+
expect(result.sql).toContain("`priority` DESC");
|
|
350
|
+
expect(result.sql).toContain("`createdAt` ASC");
|
|
350
351
|
});
|
|
351
352
|
|
|
352
|
-
test(
|
|
353
|
+
test("无方向标识应抛错", () => {
|
|
353
354
|
// **当前实现**:要求必须有 #ASC 或 #DESC
|
|
354
355
|
|
|
355
356
|
const builder = new SqlBuilder();
|
|
356
357
|
|
|
357
358
|
expect(() => {
|
|
358
|
-
builder.select([
|
|
359
|
+
builder.select(["*"]).from("users").orderBy(["createdAt"]).toSelectSql();
|
|
359
360
|
}).toThrow('orderBy 字段必须是 "字段#方向" 格式的字符串');
|
|
360
361
|
});
|
|
361
362
|
|
|
362
|
-
test(
|
|
363
|
+
test("字段名当前不转换为下划线", () => {
|
|
363
364
|
const builder = new SqlBuilder();
|
|
364
|
-
const result = builder.select([
|
|
365
|
+
const result = builder.select(["*"]).from("users").orderBy(["createdAt#DESC", "userId#ASC"]).toSelectSql();
|
|
365
366
|
|
|
366
367
|
// 当前实现不转换驼峰
|
|
367
|
-
expect(result.sql).toContain(
|
|
368
|
-
expect(result.sql).toContain(
|
|
368
|
+
expect(result.sql).toContain("createdAt");
|
|
369
|
+
expect(result.sql).toContain("userId");
|
|
369
370
|
});
|
|
370
371
|
});
|
|
371
372
|
|
|
372
|
-
describe(
|
|
373
|
-
test(
|
|
373
|
+
describe("SqlBuilder - LIMIT 和 OFFSET", () => {
|
|
374
|
+
test("LIMIT 应生成正确的 SQL", () => {
|
|
374
375
|
const builder = new SqlBuilder();
|
|
375
|
-
const result = builder.select([
|
|
376
|
+
const result = builder.select(["*"]).from("users").limit(10).toSelectSql();
|
|
376
377
|
|
|
377
|
-
expect(result.sql).toContain(
|
|
378
|
+
expect(result.sql).toContain("LIMIT 10");
|
|
378
379
|
});
|
|
379
380
|
|
|
380
|
-
test(
|
|
381
|
+
test("OFFSET 单独使用时无效(需要配合LIMIT)", () => {
|
|
381
382
|
const builder = new SqlBuilder();
|
|
382
|
-
const result = builder.select([
|
|
383
|
+
const result = builder.select(["*"]).from("users").offset(20).toSelectSql();
|
|
383
384
|
|
|
384
385
|
// 当前实现:单独使用OFFSET不生效
|
|
385
|
-
expect(result.sql).not.toContain(
|
|
386
|
+
expect(result.sql).not.toContain("OFFSET");
|
|
386
387
|
});
|
|
387
388
|
|
|
388
|
-
test(
|
|
389
|
+
test("LIMIT + OFFSET 组合", () => {
|
|
389
390
|
const builder = new SqlBuilder();
|
|
390
|
-
const result = builder.select([
|
|
391
|
+
const result = builder.select(["*"]).from("users").limit(10).offset(20).toSelectSql();
|
|
391
392
|
|
|
392
|
-
expect(result.sql).toContain(
|
|
393
|
-
expect(result.sql).toContain(
|
|
393
|
+
expect(result.sql).toContain("LIMIT 10");
|
|
394
|
+
expect(result.sql).toContain("OFFSET 20");
|
|
394
395
|
});
|
|
395
396
|
|
|
396
|
-
test(
|
|
397
|
+
test("LIMIT 0 应允许(查询结构不查数据)", () => {
|
|
397
398
|
const builder = new SqlBuilder();
|
|
398
|
-
const result = builder.select([
|
|
399
|
+
const result = builder.select(["*"]).from("users").limit(0).toSelectSql();
|
|
399
400
|
|
|
400
|
-
expect(result.sql).toContain(
|
|
401
|
+
expect(result.sql).toContain("LIMIT 0");
|
|
401
402
|
});
|
|
402
403
|
|
|
403
|
-
test(
|
|
404
|
+
test("LIMIT 负数应抛错", () => {
|
|
404
405
|
const builder = new SqlBuilder();
|
|
405
406
|
|
|
406
407
|
expect(() => {
|
|
407
|
-
builder.select([
|
|
408
|
-
}).toThrow(
|
|
408
|
+
builder.select(["*"]).from("users").limit(-10).toSelectSql();
|
|
409
|
+
}).toThrow("LIMIT 数量必须是非负数");
|
|
409
410
|
});
|
|
410
411
|
|
|
411
|
-
test(
|
|
412
|
+
test("OFFSET 负数应抛错", () => {
|
|
412
413
|
const builder = new SqlBuilder();
|
|
413
414
|
|
|
414
415
|
expect(() => {
|
|
415
|
-
builder.select([
|
|
416
|
-
}).toThrow(
|
|
416
|
+
builder.select(["*"]).from("users").offset(-5).toSelectSql();
|
|
417
|
+
}).toThrow("OFFSET 必须是非负数");
|
|
417
418
|
});
|
|
418
419
|
});
|
|
419
420
|
|
|
420
|
-
describe(
|
|
421
|
-
test(
|
|
421
|
+
describe("SqlBuilder - INSERT/UPDATE/DELETE 语句", () => {
|
|
422
|
+
test("当前 SqlBuilder 不直接支持 INSERT/UPDATE/DELETE", () => {
|
|
422
423
|
// **说明**:当前 SqlBuilder 主要用于 SELECT 查询
|
|
423
424
|
// INSERT/UPDATE/DELETE 由 DbHelper 的 insData/updData/delData 方法处理
|
|
424
425
|
|
|
425
426
|
const builder = new SqlBuilder();
|
|
426
427
|
|
|
427
428
|
// 验证没有这些方法
|
|
428
|
-
expect(typeof (builder as any).insert).toBe(
|
|
429
|
-
expect(typeof (builder as any).update).toBe(
|
|
430
|
-
expect(typeof (builder as any).delete).toBe(
|
|
429
|
+
expect(typeof (builder as any).insert).toBe("undefined");
|
|
430
|
+
expect(typeof (builder as any).update).toBe("undefined");
|
|
431
|
+
expect(typeof (builder as any).delete).toBe("undefined");
|
|
431
432
|
});
|
|
432
433
|
});
|
|
433
434
|
|
|
434
|
-
describe(
|
|
435
|
-
test(
|
|
435
|
+
describe("SqlBuilder - SQL 注入防护", () => {
|
|
436
|
+
test("参数化查询应防止 SQL 注入", () => {
|
|
436
437
|
const builder = new SqlBuilder();
|
|
437
438
|
const maliciousInput = "' OR '1'='1";
|
|
438
439
|
|
|
439
|
-
const result = builder.select([
|
|
440
|
+
const result = builder.select(["*"]).from("users").where({ name: maliciousInput }).toSelectSql();
|
|
440
441
|
|
|
441
442
|
// 参数应该被正确转义
|
|
442
443
|
expect(result.params).toEqual([maliciousInput]);
|
|
443
444
|
expect(result.sql).not.toContain("' OR '1'='1");
|
|
444
|
-
expect(result.sql).toContain(
|
|
445
|
+
expect(result.sql).toContain("?"); // 使用占位符
|
|
445
446
|
});
|
|
446
447
|
|
|
447
|
-
test(
|
|
448
|
+
test("字段名不应允许用户输入", () => {
|
|
448
449
|
// **潜在风险**:如果字段名来自用户输入
|
|
449
|
-
const userInput =
|
|
450
|
+
const userInput = "name; DROP TABLE users; --";
|
|
451
|
+
expect(userInput).toContain("DROP TABLE");
|
|
450
452
|
|
|
451
|
-
const builder = new SqlBuilder();
|
|
452
453
|
// 字段名应该从白名单选择,不应直接使用用户输入
|
|
453
454
|
// const result = builder.select([userInput]).from('users').toSelectSql();
|
|
454
455
|
|
|
455
456
|
// **建议**:字段名应该验证是否在允许的字段列表中
|
|
456
457
|
});
|
|
457
458
|
|
|
458
|
-
test(
|
|
459
|
+
test("表名不应允许用户输入", () => {
|
|
459
460
|
// **潜在风险**:表名来自用户输入
|
|
460
|
-
const userInput =
|
|
461
|
+
const userInput = "users; DROP TABLE sensitive_data; --";
|
|
462
|
+
expect(userInput).toContain("DROP TABLE");
|
|
461
463
|
|
|
462
464
|
// **建议**:表名应该从白名单选择
|
|
463
465
|
});
|
|
464
466
|
});
|
|
465
467
|
|
|
466
|
-
describe(
|
|
467
|
-
test(
|
|
468
|
+
describe("SqlBuilder - reset 功能", () => {
|
|
469
|
+
test("reset 应清空所有状态", () => {
|
|
468
470
|
const builder = new SqlBuilder();
|
|
469
471
|
|
|
470
|
-
builder.select([
|
|
472
|
+
builder.select(["name"]).from("users").where({ id: 1 }).orderBy(["name#ASC"]).limit(10);
|
|
471
473
|
|
|
472
474
|
const beforeReset = builder.toSelectSql();
|
|
473
475
|
expect(beforeReset.sql).toBeDefined();
|
|
@@ -475,56 +477,56 @@ describe('SqlBuilder - reset 功能', () => {
|
|
|
475
477
|
builder.reset();
|
|
476
478
|
|
|
477
479
|
// reset 后应该是空状态
|
|
478
|
-
builder.select([
|
|
480
|
+
builder.select(["email"]).from("profiles");
|
|
479
481
|
const afterReset = builder.toSelectSql();
|
|
480
482
|
|
|
481
|
-
expect(afterReset.sql).not.toContain(
|
|
482
|
-
expect(afterReset.sql).toContain(
|
|
483
|
-
expect(afterReset.sql).not.toContain(
|
|
483
|
+
expect(afterReset.sql).not.toContain("users");
|
|
484
|
+
expect(afterReset.sql).toContain("profiles");
|
|
485
|
+
expect(afterReset.sql).not.toContain("LIMIT");
|
|
484
486
|
});
|
|
485
487
|
|
|
486
|
-
test(
|
|
488
|
+
test("reset 应支持链式调用", () => {
|
|
487
489
|
const builder = new SqlBuilder();
|
|
488
490
|
|
|
489
|
-
const result = builder.select([
|
|
491
|
+
const result = builder.select(["*"]).from("users").reset().select(["id"]).from("posts").toSelectSql();
|
|
490
492
|
|
|
491
|
-
expect(result.sql).toContain(
|
|
492
|
-
expect(result.sql).not.toContain(
|
|
493
|
+
expect(result.sql).toContain("posts");
|
|
494
|
+
expect(result.sql).not.toContain("users");
|
|
493
495
|
});
|
|
494
496
|
});
|
|
495
497
|
|
|
496
|
-
describe(
|
|
497
|
-
test(
|
|
498
|
+
describe("SqlBuilder - 代码逻辑问题分析", () => {
|
|
499
|
+
test("问题1:$in 空数组会生成错误的 SQL", () => {
|
|
498
500
|
// **问题**:WHERE id IN () 是非法的 SQL 语法
|
|
499
501
|
// **建议**:检测到空数组应跳过条件或抛出错误
|
|
500
502
|
|
|
501
503
|
const mockWhere = (values: any[]) => {
|
|
502
504
|
if (values.length === 0) {
|
|
503
|
-
throw new Error(
|
|
505
|
+
throw new Error("$in 操作符不能使用空数组");
|
|
504
506
|
}
|
|
505
|
-
return `id IN (${values.map(() =>
|
|
507
|
+
return `id IN (${values.map(() => "?").join(", ")})`;
|
|
506
508
|
};
|
|
507
509
|
|
|
508
|
-
expect(() => mockWhere([])).toThrow(
|
|
509
|
-
expect(mockWhere([1, 2])).toBe(
|
|
510
|
+
expect(() => mockWhere([])).toThrow("$in 操作符不能使用空数组");
|
|
511
|
+
expect(mockWhere([1, 2])).toBe("id IN (?, ?)");
|
|
510
512
|
});
|
|
511
513
|
|
|
512
|
-
test(
|
|
514
|
+
test("问题2:LIMIT 和 OFFSET 没有验证", () => {
|
|
513
515
|
// **问题**:负数会导致 SQL 错误
|
|
514
516
|
// **建议**:添加参数验证
|
|
515
517
|
|
|
516
518
|
const mockLimit = (value: number) => {
|
|
517
519
|
if (value < 0) {
|
|
518
|
-
throw new Error(
|
|
520
|
+
throw new Error("LIMIT 必须 >= 0");
|
|
519
521
|
}
|
|
520
522
|
return `LIMIT ${value}`;
|
|
521
523
|
};
|
|
522
524
|
|
|
523
|
-
expect(() => mockLimit(-10)).toThrow(
|
|
524
|
-
expect(mockLimit(10)).toBe(
|
|
525
|
+
expect(() => mockLimit(-10)).toThrow("LIMIT 必须 >= 0");
|
|
526
|
+
expect(mockLimit(10)).toBe("LIMIT 10");
|
|
525
527
|
});
|
|
526
528
|
|
|
527
|
-
test(
|
|
529
|
+
test("问题3:无 WHERE 的 UPDATE 和 DELETE 很危险", () => {
|
|
528
530
|
// **问题**:容易误操作导致数据丢失
|
|
529
531
|
// **建议**:要求必须有 WHERE 条件或明确的标志
|
|
530
532
|
|
|
@@ -532,37 +534,37 @@ describe('SqlBuilder - 代码逻辑问题分析', () => {
|
|
|
532
534
|
const hasWhere = where && Object.keys(where).length > 0;
|
|
533
535
|
|
|
534
536
|
if (!hasWhere && !allowNoWhere) {
|
|
535
|
-
throw new Error(
|
|
537
|
+
throw new Error("UPDATE 必须有 WHERE 条件。如果确实要更新所有行,请设置 allowNoWhere: true");
|
|
536
538
|
}
|
|
537
539
|
|
|
538
|
-
return
|
|
540
|
+
return "UPDATE users SET status = ?";
|
|
539
541
|
};
|
|
540
542
|
|
|
541
|
-
expect(() => mockUpdate({})).toThrow(
|
|
543
|
+
expect(() => mockUpdate({})).toThrow("UPDATE 必须有 WHERE 条件");
|
|
542
544
|
expect(mockUpdate({}, true)).toBeDefined(); // 明确允许
|
|
543
545
|
expect(mockUpdate({ id: 1 })).toBeDefined(); // 有 WHERE
|
|
544
546
|
});
|
|
545
547
|
|
|
546
|
-
test(
|
|
548
|
+
test("问题4:$between 参数验证不足", () => {
|
|
547
549
|
// **问题**:应该验证是否为数组且长度为 2
|
|
548
550
|
// **建议**:添加严格的参数检查
|
|
549
551
|
|
|
550
552
|
const mockBetween = (value: any) => {
|
|
551
553
|
if (!Array.isArray(value)) {
|
|
552
|
-
throw new Error(
|
|
554
|
+
throw new Error("$between 必须是数组");
|
|
553
555
|
}
|
|
554
556
|
if (value.length !== 2) {
|
|
555
|
-
throw new Error(
|
|
557
|
+
throw new Error("$between 数组长度必须为 2");
|
|
556
558
|
}
|
|
557
559
|
return `age BETWEEN ? AND ?`;
|
|
558
560
|
};
|
|
559
561
|
|
|
560
|
-
expect(() => mockBetween(18)).toThrow(
|
|
561
|
-
expect(() => mockBetween([18])).toThrow(
|
|
562
|
+
expect(() => mockBetween(18)).toThrow("$between 必须是数组");
|
|
563
|
+
expect(() => mockBetween([18])).toThrow("$between 数组长度必须为 2");
|
|
562
564
|
expect(mockBetween([18, 65])).toBeDefined();
|
|
563
565
|
});
|
|
564
566
|
|
|
565
|
-
test(
|
|
567
|
+
test("问题5:字段名转义可能影响性能", () => {
|
|
566
568
|
// **问题**:每次查询都要转义字段名
|
|
567
569
|
// **建议**:可以缓存转义结果
|
|
568
570
|
|
|
@@ -573,19 +575,19 @@ describe('SqlBuilder - 代码逻辑问题分析', () => {
|
|
|
573
575
|
return escapeCache.get(field)!;
|
|
574
576
|
}
|
|
575
577
|
|
|
576
|
-
const escaped = field ===
|
|
578
|
+
const escaped = field === "*" ? field : `\`${field}\``;
|
|
577
579
|
escapeCache.set(field, escaped);
|
|
578
580
|
return escaped;
|
|
579
581
|
};
|
|
580
582
|
|
|
581
583
|
// 第一次转义
|
|
582
584
|
const t1 = performance.now();
|
|
583
|
-
mockEscape(
|
|
585
|
+
mockEscape("user_name");
|
|
584
586
|
const time1 = performance.now() - t1;
|
|
585
587
|
|
|
586
588
|
// 第二次应该更快(从缓存读取)
|
|
587
589
|
const t2 = performance.now();
|
|
588
|
-
mockEscape(
|
|
590
|
+
mockEscape("user_name");
|
|
589
591
|
const time2 = performance.now() - t2;
|
|
590
592
|
|
|
591
593
|
console.log(`首次转义: ${time1}ms, 缓存命中: ${time2}ms`);
|