befly 2.0.10 → 2.0.11

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.
Files changed (3) hide show
  1. package/package.json +2 -2
  2. package/plugins/db.js +70 -12
  3. package/utils/curd.js +111 -28
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "befly",
3
- "version": "2.0.10",
3
+ "version": "2.0.11",
4
4
  "description": "Buma - 为 Bun 专属打造的 API 接口框架核心引擎",
5
5
  "type": "module",
6
6
  "private": false,
@@ -51,5 +51,5 @@
51
51
  "README.md",
52
52
  "vitest.config.js"
53
53
  ],
54
- "gitHead": "e87ddebec60d7dcde96510b2177ec23719972f54"
54
+ "gitHead": "b4be4ddfba91544b051e0c0f379c14de46cbdac9"
55
55
  }
package/plugins/db.js CHANGED
@@ -78,7 +78,7 @@ export default {
78
78
  return createQueryBuilder();
79
79
  }
80
80
 
81
- // 私有方法:通用数据处理函数 - 自动添加ID和时间戳
81
+ // 私有方法:通用数据处理函数 - 自动添加ID、时间戳和状态
82
82
  async #processDataForInsert(data) {
83
83
  const now = Date.now();
84
84
 
@@ -87,6 +87,7 @@ export default {
87
87
  data.map(async (item) => ({
88
88
  ...item,
89
89
  id: await befly.redis.genTimeID(),
90
+ state: item.state !== undefined ? item.state : 0,
90
91
  created_at: now,
91
92
  updated_at: now
92
93
  }))
@@ -95,12 +96,26 @@ export default {
95
96
  return {
96
97
  ...data,
97
98
  id: await befly.redis.genTimeID(),
99
+ state: data.state !== undefined ? data.state : 0,
98
100
  created_at: now,
99
101
  updated_at: now
100
102
  };
101
103
  }
102
104
  }
103
105
 
106
+ // 私有方法:添加默认的state过滤条件
107
+ #addDefaultStateFilter(where = {}) {
108
+ // 检查是否已有state相关条件
109
+ const hasStateCondition = Object.keys(where).some((key) => key === 'state' || key.startsWith('state$'));
110
+
111
+ // 如果没有state条件,添加默认过滤
112
+ if (!hasStateCondition) {
113
+ return { ...where, state$ne: 2 };
114
+ }
115
+
116
+ return where;
117
+ }
118
+
104
119
  // 私有方法:执行 SQL(支持传入连接对象)
105
120
  async #executeWithConn(sql, params = [], conn = null) {
106
121
  if (!sql || typeof sql !== 'string') {
@@ -147,7 +162,9 @@ export default {
147
162
  const { where = {}, fields = '*', leftJoins = [] } = typeof options === 'object' && !Array.isArray(options) ? options : { where: options };
148
163
 
149
164
  try {
150
- const builder = createQueryBuilder().select(fields).from(table).where(where).limit(1);
165
+ // 添加默认的state过滤条件
166
+ const filteredWhere = this.#addDefaultStateFilter(where);
167
+ const builder = createQueryBuilder().select(fields).from(table).where(filteredWhere).limit(1);
151
168
 
152
169
  // 添加 LEFT JOIN
153
170
  leftJoins.forEach((join) => {
@@ -179,7 +196,9 @@ export default {
179
196
  const { where = {}, fields = '*', leftJoins = [], orderBy = [], groupBy = [], having = [], page = 1, pageSize = 10 } = options;
180
197
 
181
198
  try {
182
- const builder = createQueryBuilder().select(fields).from(table).where(where);
199
+ // 添加默认的state过滤条件
200
+ const filteredWhere = this.#addDefaultStateFilter(where);
201
+ const builder = createQueryBuilder().select(fields).from(table).where(filteredWhere);
183
202
 
184
203
  // 添加 LEFT JOIN
185
204
  leftJoins.forEach((join) => {
@@ -221,7 +240,7 @@ export default {
221
240
  // 获取总数(如果需要分页)
222
241
  let total = 0;
223
242
  if (numPage > 0 && numPageSize > 0) {
224
- const countBuilder = createQueryBuilder().from(table).where(where);
243
+ const countBuilder = createQueryBuilder().from(table).where(filteredWhere);
225
244
 
226
245
  // 计算总数时也要包含 JOIN
227
246
  leftJoins.forEach((join) => {
@@ -261,7 +280,9 @@ export default {
261
280
  const { where = {}, fields = '*', leftJoins = [], orderBy = [] } = typeof options === 'object' && !Array.isArray(options) ? options : { where: options };
262
281
 
263
282
  try {
264
- const builder = createQueryBuilder().select(fields).from(table).where(where);
283
+ // 添加默认的state过滤条件
284
+ const filteredWhere = this.#addDefaultStateFilter(where);
285
+ const builder = createQueryBuilder().select(fields).from(table).where(filteredWhere);
265
286
 
266
287
  // 添加 LEFT JOIN
267
288
  leftJoins.forEach((join) => {
@@ -310,7 +331,7 @@ export default {
310
331
  }
311
332
 
312
333
  // 私有方法:更新数据(支持传入连接对象)
313
- async #upDataWithConn(table, data, where, conn = null) {
334
+ async #updDataWithConn(table, data, where, conn = null) {
314
335
  if (!table || typeof table !== 'string') {
315
336
  throw new Error('表名是必需的');
316
337
  }
@@ -337,7 +358,7 @@ export default {
337
358
  const { sql, params } = builder.toUpdateSql(table, updateData);
338
359
  return await this.#executeWithConn(sql, params, conn);
339
360
  } catch (error) {
340
- Logger.error('upData 执行失败:', error);
361
+ Logger.error('updData 执行失败:', error);
341
362
  throw error;
342
363
  }
343
364
  }
@@ -362,6 +383,32 @@ export default {
362
383
  }
363
384
  }
364
385
 
386
+ // 私有方法:软删除数据(支持传入连接对象)
387
+ async #delData2WithConn(table, where, conn = null) {
388
+ if (!table || typeof table !== 'string') {
389
+ throw new Error('表名是必需的');
390
+ }
391
+
392
+ if (!where) {
393
+ throw new Error('软删除操作需要 WHERE 条件');
394
+ }
395
+
396
+ try {
397
+ // 软删除:将 state 设置为 2,同时更新 updated_at
398
+ const updateData = {
399
+ state: 2,
400
+ updated_at: Date.now()
401
+ };
402
+
403
+ const builder = createQueryBuilder().where(where);
404
+ const { sql, params } = builder.toUpdateSql(table, updateData);
405
+ return await this.#executeWithConn(sql, params, conn);
406
+ } catch (error) {
407
+ Logger.error('delData2 执行失败:', error);
408
+ throw error;
409
+ }
410
+ }
411
+
365
412
  // 私有方法:批量插入(支持传入连接对象)
366
413
  async #insBatchWithConn(table, dataArray, conn = null) {
367
414
  if (!table || typeof table !== 'string') {
@@ -392,7 +439,9 @@ export default {
392
439
  const { where = {}, leftJoins = [] } = typeof options === 'object' && !Array.isArray(options) ? options : { where: options };
393
440
 
394
441
  try {
395
- const builder = createQueryBuilder().from(table).where(where);
442
+ // 添加默认的state过滤条件
443
+ const filteredWhere = this.#addDefaultStateFilter(where);
444
+ const builder = createQueryBuilder().from(table).where(filteredWhere);
396
445
 
397
446
  // 添加 LEFT JOIN
398
447
  leftJoins.forEach((join) => {
@@ -441,8 +490,8 @@ export default {
441
490
  }
442
491
 
443
492
  // 更新数据 - 增强版,自动添加 updated_at,过滤敏感字段
444
- async upData(table, data, where) {
445
- return await this.#upDataWithConn(table, data, where);
493
+ async updData(table, data, where) {
494
+ return await this.#updDataWithConn(table, data, where);
446
495
  }
447
496
 
448
497
  // 删除数据
@@ -450,6 +499,11 @@ export default {
450
499
  return await this.#delDataWithConn(table, where);
451
500
  }
452
501
 
502
+ // 软删除数据 - 将 state 设置为 2
503
+ async delData2(table, where) {
504
+ return await this.#delData2WithConn(table, where);
505
+ }
506
+
453
507
  // 批量插入 - 增强版,自动添加 ID 和时间戳
454
508
  async insBatch(table, dataArray) {
455
509
  return await this.#insBatchWithConn(table, dataArray);
@@ -498,14 +552,18 @@ export default {
498
552
  return await this.#insDataWithConn(table, data, conn);
499
553
  },
500
554
 
501
- upData: async (table, data, where) => {
502
- return await this.#upDataWithConn(table, data, where, conn);
555
+ updData: async (table, data, where) => {
556
+ return await this.#updDataWithConn(table, data, where, conn);
503
557
  },
504
558
 
505
559
  delData: async (table, where) => {
506
560
  return await this.#delDataWithConn(table, where, conn);
507
561
  },
508
562
 
563
+ delData2: async (table, where) => {
564
+ return await this.#delData2WithConn(table, where, conn);
565
+ },
566
+
509
567
  getCount: async (table, options = {}) => {
510
568
  return await this.#getCountWithConn(table, options, conn);
511
569
  },
package/utils/curd.js CHANGED
@@ -20,11 +20,82 @@ export class SqlBuilder {
20
20
  return this;
21
21
  }
22
22
 
23
+ // 字段转义方法 - 处理字段名和表名的着重号转义
24
+ _escapeField(field) {
25
+ if (typeof field !== 'string') {
26
+ return field;
27
+ }
28
+
29
+ // 去除前后空格
30
+ field = field.trim();
31
+
32
+ // 如果是 * 或已经有着重号,直接返回
33
+ if (field === '*' || field.startsWith('`') || field.includes('(')) {
34
+ return field;
35
+ }
36
+
37
+ // 处理别名(AS关键字)
38
+ if (field.toUpperCase().includes(' AS ')) {
39
+ const parts = field.split(/\s+AS\s+/i);
40
+ const fieldPart = parts[0].trim();
41
+ const aliasPart = parts[1].trim();
42
+ return `${this._escapeField(fieldPart)} AS ${aliasPart}`;
43
+ }
44
+
45
+ // 处理表名.字段名的情况(多表联查)
46
+ if (field.includes('.')) {
47
+ const parts = field.split('.');
48
+ return parts
49
+ .map((part) => {
50
+ part = part.trim();
51
+ // 如果是 * 或已经有着重号,不再处理
52
+ if (part === '*' || part.startsWith('`')) {
53
+ return part;
54
+ }
55
+ return `\`${part}\``;
56
+ })
57
+ .join('.');
58
+ }
59
+
60
+ // 处理单个字段名
61
+ return `\`${field}\``;
62
+ }
63
+
64
+ // 转义表名
65
+ _escapeTable(table) {
66
+ if (typeof table !== 'string') {
67
+ return table;
68
+ }
69
+
70
+ table = table.trim();
71
+
72
+ // 如果已经有着重号,直接返回
73
+ if (table.startsWith('`')) {
74
+ return table;
75
+ }
76
+
77
+ // 处理表别名(表名 + 空格 + 别名)
78
+ if (table.includes(' ')) {
79
+ const parts = table.split(/\s+/);
80
+ if (parts.length === 2) {
81
+ // 只有表名和别名的情况
82
+ const tableName = parts[0].trim();
83
+ const alias = parts[1].trim();
84
+ return `\`${tableName}\` ${alias}`;
85
+ } else {
86
+ // 复杂情况,直接返回
87
+ return table;
88
+ }
89
+ }
90
+
91
+ return `\`${table}\``;
92
+ }
93
+
23
94
  select(fields = '*') {
24
95
  if (Array.isArray(fields)) {
25
- this._select = [...this._select, ...fields];
96
+ this._select = [...this._select, ...fields.map((field) => this._escapeField(field))];
26
97
  } else if (typeof fields === 'string') {
27
- this._select.push(fields);
98
+ this._select.push(this._escapeField(fields));
28
99
  } else {
29
100
  throw new Error('SELECT fields must be string or array');
30
101
  }
@@ -35,7 +106,7 @@ export class SqlBuilder {
35
106
  if (typeof table !== 'string' || !table.trim()) {
36
107
  throw new Error('FROM table must be a non-empty string');
37
108
  }
38
- this._from = table.trim();
109
+ this._from = this._escapeTable(table.trim());
39
110
  return this;
40
111
  }
41
112
 
@@ -86,6 +157,7 @@ export class SqlBuilder {
86
157
  // 一级属性格式:age$gt, role$in 等
87
158
  const lastDollarIndex = key.lastIndexOf('$');
88
159
  const fieldName = key.substring(0, lastDollarIndex);
160
+ const escapedFieldName = this._escapeField(fieldName);
89
161
  const operator = '$' + key.substring(lastDollarIndex + 1);
90
162
 
91
163
  this._validateParam(value);
@@ -93,13 +165,13 @@ export class SqlBuilder {
93
165
  switch (operator) {
94
166
  case '$ne':
95
167
  case '$not':
96
- this._where.push(`${fieldName} != ?`);
168
+ this._where.push(`${escapedFieldName} != ?`);
97
169
  this._params.push(value);
98
170
  break;
99
171
  case '$in':
100
172
  if (Array.isArray(value) && value.length > 0) {
101
173
  const placeholders = value.map(() => '?').join(',');
102
- this._where.push(`${fieldName} IN (${placeholders})`);
174
+ this._where.push(`${escapedFieldName} IN (${placeholders})`);
103
175
  this._params.push(...value);
104
176
  }
105
177
  break;
@@ -107,64 +179,65 @@ export class SqlBuilder {
107
179
  case '$notIn':
108
180
  if (Array.isArray(value) && value.length > 0) {
109
181
  const placeholders = value.map(() => '?').join(',');
110
- this._where.push(`${fieldName} NOT IN (${placeholders})`);
182
+ this._where.push(`${escapedFieldName} NOT IN (${placeholders})`);
111
183
  this._params.push(...value);
112
184
  }
113
185
  break;
114
186
  case '$like':
115
- this._where.push(`${fieldName} LIKE ?`);
187
+ this._where.push(`${escapedFieldName} LIKE ?`);
116
188
  this._params.push(value);
117
189
  break;
118
190
  case '$notLike':
119
- this._where.push(`${fieldName} NOT LIKE ?`);
191
+ this._where.push(`${escapedFieldName} NOT LIKE ?`);
120
192
  this._params.push(value);
121
193
  break;
122
194
  case '$gt':
123
- this._where.push(`${fieldName} > ?`);
195
+ this._where.push(`${escapedFieldName} > ?`);
124
196
  this._params.push(value);
125
197
  break;
126
198
  case '$gte':
127
- this._where.push(`${fieldName} >= ?`);
199
+ this._where.push(`${escapedFieldName} >= ?`);
128
200
  this._params.push(value);
129
201
  break;
130
202
  case '$lt':
131
- this._where.push(`${fieldName} < ?`);
203
+ this._where.push(`${escapedFieldName} < ?`);
132
204
  this._params.push(value);
133
205
  break;
134
206
  case '$lte':
135
- this._where.push(`${fieldName} <= ?`);
207
+ this._where.push(`${escapedFieldName} <= ?`);
136
208
  this._params.push(value);
137
209
  break;
138
210
  case '$between':
139
211
  if (Array.isArray(value) && value.length === 2) {
140
- this._where.push(`${fieldName} BETWEEN ? AND ?`);
212
+ this._where.push(`${escapedFieldName} BETWEEN ? AND ?`);
141
213
  this._params.push(value[0], value[1]);
142
214
  }
143
215
  break;
144
216
  case '$notBetween':
145
217
  if (Array.isArray(value) && value.length === 2) {
146
- this._where.push(`${fieldName} NOT BETWEEN ? AND ?`);
218
+ this._where.push(`${escapedFieldName} NOT BETWEEN ? AND ?`);
147
219
  this._params.push(value[0], value[1]);
148
220
  }
149
221
  break;
150
222
  case '$null':
151
223
  if (value === true) {
152
- this._where.push(`${fieldName} IS NULL`);
224
+ this._where.push(`${escapedFieldName} IS NULL`);
153
225
  }
154
226
  break;
155
227
  case '$notNull':
156
228
  if (value === true) {
157
- this._where.push(`${fieldName} IS NOT NULL`);
229
+ this._where.push(`${escapedFieldName} IS NOT NULL`);
158
230
  }
159
231
  break;
160
232
  default:
161
- this._where.push(`${fieldName} = ?`);
233
+ this._where.push(`${escapedFieldName} = ?`);
162
234
  this._params.push(value);
163
235
  }
164
236
  } else {
165
237
  // 简单的等于条件
166
238
  this._validateParam(value);
167
- this._where.push(`${key} = ?`);
239
+ const escapedKey = this._escapeField(key);
240
+ this._where.push(`${escapedKey} = ?`);
168
241
  this._params.push(value);
169
242
  }
170
243
  });
@@ -176,7 +249,8 @@ export class SqlBuilder {
176
249
  this._processWhereConditions(condition);
177
250
  } else if (value !== null) {
178
251
  this._validateParam(value);
179
- this._where.push(`${condition} = ?`);
252
+ const escapedCondition = this._escapeField(condition);
253
+ this._where.push(`${escapedCondition} = ?`);
180
254
  this._params.push(value);
181
255
  } else if (typeof condition === 'string') {
182
256
  this._where.push(condition);
@@ -188,7 +262,8 @@ export class SqlBuilder {
188
262
  if (typeof table !== 'string' || typeof on !== 'string') {
189
263
  throw new Error('JOIN table and condition must be strings');
190
264
  }
191
- this._joins.push(`LEFT JOIN ${table} ON ${on}`);
265
+ const escapedTable = this._escapeTable(table);
266
+ this._joins.push(`LEFT JOIN ${escapedTable} ON ${on}`);
192
267
  return this;
193
268
  }
194
269
 
@@ -214,7 +289,8 @@ export class SqlBuilder {
214
289
  throw new Error('ORDER BY direction must be ASC or DESC');
215
290
  }
216
291
 
217
- this._orderBy.push(`${cleanField} ${cleanDir}`);
292
+ const escapedField = this._escapeField(cleanField);
293
+ this._orderBy.push(`${escapedField} ${cleanDir}`);
218
294
  });
219
295
 
220
296
  return this;
@@ -222,9 +298,10 @@ export class SqlBuilder {
222
298
 
223
299
  groupBy(field) {
224
300
  if (Array.isArray(field)) {
225
- this._groupBy = [...this._groupBy, ...field.filter((f) => typeof f === 'string')];
301
+ const escapedFields = field.filter((f) => typeof f === 'string').map((f) => this._escapeField(f));
302
+ this._groupBy = [...this._groupBy, ...escapedFields];
226
303
  } else if (typeof field === 'string') {
227
- this._groupBy.push(field);
304
+ this._groupBy.push(this._escapeField(field));
228
305
  }
229
306
  return this;
230
307
  }
@@ -309,6 +386,8 @@ export class SqlBuilder {
309
386
  throw new Error('Data is required for INSERT');
310
387
  }
311
388
 
389
+ const escapedTable = this._escapeTable(table);
390
+
312
391
  if (Array.isArray(data)) {
313
392
  if (data.length === 0) {
314
393
  throw new Error('Insert data cannot be empty');
@@ -319,10 +398,11 @@ export class SqlBuilder {
319
398
  throw new Error('Insert data must have at least one field');
320
399
  }
321
400
 
401
+ const escapedFields = fields.map((field) => this._escapeField(field));
322
402
  const placeholders = fields.map(() => '?').join(', ');
323
403
  const values = data.map(() => `(${placeholders})`).join(', ');
324
404
 
325
- const sql = `INSERT INTO ${table} (${fields.join(', ')}) VALUES ${values}`;
405
+ const sql = `INSERT INTO ${escapedTable} (${escapedFields.join(', ')}) VALUES ${values}`;
326
406
  const params = data.flatMap((row) => fields.map((field) => row[field]));
327
407
 
328
408
  return { sql, params };
@@ -332,8 +412,9 @@ export class SqlBuilder {
332
412
  throw new Error('Insert data must have at least one field');
333
413
  }
334
414
 
415
+ const escapedFields = fields.map((field) => this._escapeField(field));
335
416
  const placeholders = fields.map(() => '?').join(', ');
336
- const sql = `INSERT INTO ${table} (${fields.join(', ')}) VALUES (${placeholders})`;
417
+ const sql = `INSERT INTO ${escapedTable} (${escapedFields.join(', ')}) VALUES (${placeholders})`;
337
418
  const params = fields.map((field) => data[field]);
338
419
 
339
420
  return { sql, params };
@@ -355,10 +436,11 @@ export class SqlBuilder {
355
436
  throw new Error('Update data must have at least one field');
356
437
  }
357
438
 
358
- const setFields = fields.map((field) => `${field} = ?`);
439
+ const escapedTable = this._escapeTable(table);
440
+ const setFields = fields.map((field) => `${this._escapeField(field)} = ?`);
359
441
  const params = [...Object.values(data), ...this._params];
360
442
 
361
- let sql = `UPDATE ${table} SET ${setFields.join(', ')}`;
443
+ let sql = `UPDATE ${escapedTable} SET ${setFields.join(', ')}`;
362
444
 
363
445
  if (this._where.length > 0) {
364
446
  sql += ' WHERE ' + this._where.join(' AND ');
@@ -375,7 +457,8 @@ export class SqlBuilder {
375
457
  throw new Error('Table name is required for DELETE');
376
458
  }
377
459
 
378
- let sql = `DELETE FROM ${table}`;
460
+ const escapedTable = this._escapeTable(table);
461
+ let sql = `DELETE FROM ${escapedTable}`;
379
462
 
380
463
  if (this._where.length > 0) {
381
464
  sql += ' WHERE ' + this._where.join(' AND ');