befly 3.2.0 → 3.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (73) hide show
  1. package/bin/index.ts +138 -0
  2. package/checks/conflict.ts +35 -25
  3. package/checks/table.ts +6 -6
  4. package/commands/addon.ts +57 -0
  5. package/commands/build.ts +74 -0
  6. package/commands/dev.ts +94 -0
  7. package/commands/index.ts +252 -0
  8. package/commands/script.ts +308 -0
  9. package/commands/start.ts +80 -0
  10. package/commands/syncApi.ts +328 -0
  11. package/{scripts → commands}/syncDb/apply.ts +2 -2
  12. package/{scripts → commands}/syncDb/constants.ts +13 -7
  13. package/{scripts → commands}/syncDb/ddl.ts +7 -5
  14. package/{scripts → commands}/syncDb/helpers.ts +18 -18
  15. package/{scripts → commands}/syncDb/index.ts +37 -23
  16. package/{scripts → commands}/syncDb/sqlite.ts +1 -1
  17. package/{scripts → commands}/syncDb/state.ts +10 -4
  18. package/{scripts → commands}/syncDb/table.ts +7 -7
  19. package/{scripts → commands}/syncDb/tableCreate.ts +7 -6
  20. package/{scripts → commands}/syncDb/types.ts +5 -5
  21. package/{scripts → commands}/syncDb/version.ts +1 -1
  22. package/commands/syncDb.ts +35 -0
  23. package/commands/syncDev.ts +174 -0
  24. package/commands/syncMenu.ts +368 -0
  25. package/config/env.ts +4 -4
  26. package/config/menu.json +67 -0
  27. package/{utils/crypto.ts → lib/cipher.ts} +16 -67
  28. package/lib/database.ts +296 -0
  29. package/{utils → lib}/dbHelper.ts +102 -56
  30. package/{utils → lib}/jwt.ts +124 -151
  31. package/{utils → lib}/logger.ts +47 -24
  32. package/lib/middleware.ts +271 -0
  33. package/{utils → lib}/redisHelper.ts +4 -4
  34. package/{utils/validate.ts → lib/validator.ts} +101 -78
  35. package/lifecycle/bootstrap.ts +63 -0
  36. package/lifecycle/checker.ts +165 -0
  37. package/lifecycle/cluster.ts +241 -0
  38. package/lifecycle/lifecycle.ts +139 -0
  39. package/lifecycle/loader.ts +513 -0
  40. package/main.ts +14 -12
  41. package/package.json +21 -9
  42. package/paths.ts +34 -0
  43. package/plugins/cache.ts +187 -0
  44. package/plugins/db.ts +4 -4
  45. package/plugins/logger.ts +1 -1
  46. package/plugins/redis.ts +4 -4
  47. package/router/api.ts +155 -0
  48. package/router/root.ts +53 -0
  49. package/router/static.ts +76 -0
  50. package/types/api.d.ts +0 -36
  51. package/types/befly.d.ts +8 -6
  52. package/types/common.d.ts +1 -1
  53. package/types/context.d.ts +3 -3
  54. package/types/util.d.ts +45 -0
  55. package/util.ts +301 -0
  56. package/config/fields.ts +0 -55
  57. package/config/regexAliases.ts +0 -51
  58. package/config/reserved.ts +0 -96
  59. package/scripts/syncDb/tests/constants.test.ts +0 -105
  60. package/scripts/syncDb/tests/ddl.test.ts +0 -134
  61. package/scripts/syncDb/tests/helpers.test.ts +0 -70
  62. package/scripts/syncDb.ts +0 -10
  63. package/types/index.d.ts +0 -450
  64. package/types/index.ts +0 -438
  65. package/types/validator.ts +0 -43
  66. package/utils/colors.ts +0 -221
  67. package/utils/database.ts +0 -348
  68. package/utils/helper.ts +0 -812
  69. package/utils/index.ts +0 -33
  70. package/utils/requestContext.ts +0 -167
  71. /package/{scripts → commands}/syncDb/schema.ts +0 -0
  72. /package/{utils → lib}/sqlBuilder.ts +0 -0
  73. /package/{utils → lib}/xml.ts +0 -0
@@ -0,0 +1,296 @@
1
+ /**
2
+ * 数据库连接管理器
3
+ * 统一管理 SQL 和 Redis 连接
4
+ */
5
+
6
+ import { SQL, RedisClient } from 'bun';
7
+ import { Env } from '../config/env.js';
8
+ import { Logger } from './logger.js';
9
+ import { DbHelper } from './dbHelper.js';
10
+ import { RedisHelper } from './redisHelper.js';
11
+ import type { BeflyContext } from '../types/befly.js';
12
+ import type { SqlClientOptions } from '../types/database.js';
13
+
14
+ /**
15
+ * 数据库连接管理器
16
+ * 使用静态方法管理全局单例连接
17
+ */
18
+ export class Database {
19
+ private static sqlClient: SQL | null = null;
20
+ private static redisClient: RedisClient | null = null;
21
+ private static dbHelper: DbHelper | null = null;
22
+
23
+ // ========================================
24
+ // SQL 连接管理
25
+ // ========================================
26
+
27
+ /**
28
+ * 连接 SQL 数据库
29
+ * @param options - SQL 客户端配置选项
30
+ * @returns SQL 客户端实例
31
+ */
32
+ static async connectSql(options: SqlClientOptions = {}): Promise<SQL> {
33
+ // 构建数据库连接字符串
34
+ const type = Env.DB_TYPE || '';
35
+ const host = Env.DB_HOST || '';
36
+ const port = Env.DB_PORT;
37
+ const user = encodeURIComponent(Env.DB_USER || '');
38
+ const password = encodeURIComponent(Env.DB_PASS || '');
39
+ const database = encodeURIComponent(Env.DB_NAME || '');
40
+
41
+ let finalUrl: string;
42
+ if (type === 'sqlite') {
43
+ finalUrl = database;
44
+ } else {
45
+ if (!host || !database) {
46
+ throw new Error('数据库配置不完整,请检查环境变量');
47
+ }
48
+ finalUrl = `${type}://${user}:${password}@${host}:${port}/${database}`;
49
+ }
50
+
51
+ let sql: SQL;
52
+
53
+ if (Env.DB_TYPE === 'sqlite') {
54
+ sql = new SQL(finalUrl);
55
+ } else {
56
+ sql = new SQL({
57
+ url: finalUrl,
58
+ max: options.max ?? 1,
59
+ bigint: false,
60
+ ...options
61
+ });
62
+ }
63
+
64
+ try {
65
+ const timeout = options.connectionTimeout ?? 5000;
66
+
67
+ const healthCheckPromise = (async () => {
68
+ let version = '';
69
+ if (Env.DB_TYPE === 'sqlite') {
70
+ const v = await sql`SELECT sqlite_version() AS version`;
71
+ version = v?.[0]?.version;
72
+ } else if (Env.DB_TYPE === 'postgresql' || Env.DB_TYPE === 'postgres') {
73
+ const v = await sql`SELECT version() AS version`;
74
+ version = v?.[0]?.version;
75
+ } else {
76
+ const v = await sql`SELECT VERSION() AS version`;
77
+ version = v?.[0]?.version;
78
+ }
79
+ return version;
80
+ })();
81
+
82
+ const timeoutPromise = new Promise<never>((_, reject) => {
83
+ setTimeout(() => {
84
+ reject(new Error(`数据库连接超时 (${timeout}ms)`));
85
+ }, timeout);
86
+ });
87
+
88
+ const version = await Promise.race([healthCheckPromise, timeoutPromise]);
89
+
90
+ Logger.info(`数据库连接成功,version: ${version}`);
91
+
92
+ this.sqlClient = sql;
93
+ return sql;
94
+ } catch (error: any) {
95
+ Logger.error('数据库连接测试失败', error);
96
+
97
+ try {
98
+ await sql.close();
99
+ } catch (cleanupError) {}
100
+
101
+ throw error;
102
+ }
103
+ }
104
+
105
+ /**
106
+ * 断开 SQL 连接
107
+ */
108
+ static async disconnectSql(): Promise<void> {
109
+ if (this.sqlClient) {
110
+ try {
111
+ await this.sqlClient.close();
112
+ Logger.info('SQL 连接已关闭');
113
+ } catch (error: any) {
114
+ Logger.warn('关闭 SQL 连接时出错:', error.message);
115
+ }
116
+ this.sqlClient = null;
117
+ }
118
+
119
+ if (this.dbHelper) {
120
+ this.dbHelper = null;
121
+ }
122
+ }
123
+
124
+ /**
125
+ * 获取 SQL 客户端实例
126
+ * @throws 如果未连接则抛出错误
127
+ */
128
+ static getSql(): SQL {
129
+ if (!this.sqlClient) {
130
+ throw new Error('SQL 客户端未连接,请先调用 Database.connectSql()');
131
+ }
132
+ return this.sqlClient;
133
+ }
134
+
135
+ /**
136
+ * 获取 DbHelper 实例
137
+ * @throws 如果未连接则抛出错误
138
+ */
139
+ static getDbHelper(befly?: BeflyContext): DbHelper {
140
+ if (!this.dbHelper) {
141
+ if (!this.sqlClient) {
142
+ throw new Error('SQL 客户端未连接,请先调用 Database.connectSql()');
143
+ }
144
+ // 创建临时 befly 上下文(仅用于 DbHelper)
145
+ const ctx = befly || {
146
+ redis: RedisHelper,
147
+ db: null as any,
148
+ tool: null as any,
149
+ logger: null as any
150
+ };
151
+ this.dbHelper = new DbHelper(ctx, this.sqlClient);
152
+ }
153
+ return this.dbHelper;
154
+ }
155
+
156
+ // ========================================
157
+ // Redis 连接管理
158
+ // ========================================
159
+
160
+ /**
161
+ * 连接 Redis
162
+ * @returns Redis 客户端实例
163
+ */
164
+ static async connectRedis(): Promise<RedisClient> {
165
+ try {
166
+ // 构建 Redis URL
167
+ const { REDIS_HOST, REDIS_PORT, REDIS_USERNAME, REDIS_PASSWORD, REDIS_DB } = Env;
168
+
169
+ let auth = '';
170
+ if (REDIS_USERNAME && REDIS_PASSWORD) {
171
+ auth = `${REDIS_USERNAME}:${REDIS_PASSWORD}@`;
172
+ } else if (REDIS_PASSWORD) {
173
+ auth = `:${REDIS_PASSWORD}@`;
174
+ }
175
+
176
+ const url = `redis://${auth}${REDIS_HOST}:${REDIS_PORT}/${REDIS_DB}`;
177
+
178
+ const redis = new RedisClient(url, {
179
+ connectionTimeout: 10000,
180
+ idleTimeout: 0,
181
+ autoReconnect: true,
182
+ maxRetries: 0,
183
+ enableOfflineQueue: true,
184
+ enableAutoPipelining: true
185
+ });
186
+
187
+ await redis.ping();
188
+ Logger.info('Redis 连接成功');
189
+
190
+ this.redisClient = redis;
191
+ return redis;
192
+ } catch (error: any) {
193
+ Logger.error('Redis 连接失败', error);
194
+ throw new Error(`Redis 连接失败: ${error.message}`);
195
+ }
196
+ }
197
+
198
+ /**
199
+ * 断开 Redis 连接
200
+ */
201
+ static async disconnectRedis(): Promise<void> {
202
+ if (this.redisClient) {
203
+ try {
204
+ this.redisClient.close();
205
+ Logger.info('Redis 连接已关闭');
206
+ } catch (error: any) {
207
+ Logger.warn('关闭 Redis 连接时出错:', error);
208
+ }
209
+ this.redisClient = null;
210
+ }
211
+ }
212
+
213
+ /**
214
+ * 获取 Redis 客户端实例
215
+ * @throws 如果未连接则抛出错误
216
+ */
217
+ static getRedis(): RedisClient {
218
+ if (!this.redisClient) {
219
+ throw new Error('Redis 客户端未连接,请先调用 Database.connectRedis()');
220
+ }
221
+ return this.redisClient;
222
+ }
223
+
224
+ // ========================================
225
+ // 统一连接管理
226
+ // ========================================
227
+
228
+ /**
229
+ * 连接所有数据库(SQL + Redis)
230
+ * @param options - 配置选项
231
+ */
232
+ static async connect(options?: { sql?: SqlClientOptions; redis?: boolean }): Promise<void> {
233
+ try {
234
+ if (options?.sql !== false) {
235
+ Logger.info('正在初始化 SQL 连接...');
236
+ await this.connectSql(options?.sql);
237
+ }
238
+
239
+ if (options?.redis !== false) {
240
+ Logger.info('正在初始化 Redis 连接...');
241
+ await this.connectRedis();
242
+ }
243
+
244
+ Logger.info('数据库连接初始化完成');
245
+ } catch (error: any) {
246
+ Logger.error('数据库初始化失败', error);
247
+ await this.disconnect();
248
+ throw error;
249
+ }
250
+ }
251
+
252
+ /**
253
+ * 断开所有数据库连接
254
+ */
255
+ static async disconnect(): Promise<void> {
256
+ await this.disconnectSql();
257
+ await this.disconnectRedis();
258
+ }
259
+
260
+ /**
261
+ * 检查连接状态
262
+ */
263
+ static isConnected(): { sql: boolean; redis: boolean } {
264
+ return {
265
+ sql: this.sqlClient !== null,
266
+ redis: this.redisClient !== null
267
+ };
268
+ }
269
+
270
+ // ========================================
271
+ // 测试辅助方法
272
+ // ========================================
273
+
274
+ /**
275
+ * 设置 mock SQL 客户端(仅用于测试)
276
+ */
277
+ static __setMockSql(mockClient: SQL): void {
278
+ this.sqlClient = mockClient;
279
+ }
280
+
281
+ /**
282
+ * 设置 mock Redis 客户端(仅用于测试)
283
+ */
284
+ static __setMockRedis(mockClient: RedisClient): void {
285
+ this.redisClient = mockClient;
286
+ }
287
+
288
+ /**
289
+ * 重置所有连接状态(仅用于测试)
290
+ */
291
+ static __reset(): void {
292
+ this.sqlClient = null;
293
+ this.redisClient = null;
294
+ this.dbHelper = null;
295
+ }
296
+ }
@@ -3,9 +3,10 @@
3
3
  * 提供数据库 CRUD 操作的封装
4
4
  */
5
5
 
6
+ import { snakeCase } from 'es-toolkit/string';
6
7
  import { SqlBuilder } from './sqlBuilder.js';
7
- import { keysToCamel, arrayKeysToCamel, keysToSnake, fieldClear, convertBigIntFields, toSnakeCase } from './helper.js';
8
- import { Logger } from './logger.js';
8
+ import { keysToCamel, arrayKeysToCamel, keysToSnake, fieldClear } from '../util.js';
9
+ import { Logger } from '../lib/logger.js';
9
10
  import type { WhereConditions } from '../types/common.js';
10
11
  import type { BeflyContext } from '../types/befly.js';
11
12
  import type { QueryOptions, InsertOptions, UpdateOptions, DeleteOptions, ListResult, TransactionCallback } from '../types/database.js';
@@ -39,7 +40,7 @@ export class DbHelper {
39
40
  if (field === '*' || field.includes('(') || field.includes(' ')) {
40
41
  return field;
41
42
  }
42
- return toSnakeCase(field);
43
+ return snakeCase(field);
43
44
  });
44
45
  }
45
46
 
@@ -51,51 +52,10 @@ export class DbHelper {
51
52
  return orderBy.map((item) => {
52
53
  if (typeof item !== 'string' || !item.includes('#')) return item;
53
54
  const [field, direction] = item.split('#');
54
- return `${toSnakeCase(field.trim())}#${direction.trim()}`;
55
+ return `${snakeCase(field.trim())}#${direction.trim()}`;
55
56
  });
56
57
  }
57
58
 
58
- /**
59
- * where 条件键名转下划线格式(私有方法,递归处理嵌套)
60
- */
61
- private whereKeysToSnake(where: any): any {
62
- if (!where || typeof where !== 'object') return where;
63
-
64
- // 处理数组($or, $and 等)
65
- if (Array.isArray(where)) {
66
- return where.map((item) => this.whereKeysToSnake(item));
67
- }
68
-
69
- const result: any = {};
70
- for (const [key, value] of Object.entries(where)) {
71
- // 保留 $or, $and 等逻辑操作符
72
- if (key === '$or' || key === '$and') {
73
- result[key] = (value as any[]).map((item) => this.whereKeysToSnake(item));
74
- continue;
75
- }
76
-
77
- // 处理带操作符的字段名(如 userId$gt)
78
- if (key.includes('$')) {
79
- const lastDollarIndex = key.lastIndexOf('$');
80
- const fieldName = key.substring(0, lastDollarIndex);
81
- const operator = key.substring(lastDollarIndex);
82
- const snakeKey = toSnakeCase(fieldName) + operator;
83
- result[snakeKey] = value;
84
- continue;
85
- }
86
-
87
- // 普通字段:转换键名,递归处理值(支持嵌套对象)
88
- const snakeKey = toSnakeCase(key);
89
- if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
90
- result[snakeKey] = this.whereKeysToSnake(value);
91
- } else {
92
- result[snakeKey] = value;
93
- }
94
- }
95
-
96
- return result;
97
- }
98
-
99
59
  /**
100
60
  * 统一的查询参数预处理方法
101
61
  */
@@ -103,7 +63,7 @@ export class DbHelper {
103
63
  const cleanWhere = this.cleanFields(options.where);
104
64
 
105
65
  return {
106
- table: toSnakeCase(options.table),
66
+ table: snakeCase(options.table),
107
67
  fields: this.fieldsToSnake(options.fields || ['*']),
108
68
  where: this.whereKeysToSnake(cleanWhere),
109
69
  orderBy: this.orderByToSnake(options.orderBy || []),
@@ -138,6 +98,92 @@ export class DbHelper {
138
98
  return fieldClear(data || ({} as T), excludeValues, keepValues);
139
99
  }
140
100
 
101
+ /**
102
+ * 转换数据库 BIGINT 字段为数字类型(私有方法)
103
+ * 当 bigint: false 时,Bun SQL 会将大于 u32 的 BIGINT 返回为字符串,此方法将其转换为 number
104
+ *
105
+ * 转换规则:
106
+ * 1. 白名单中的字段会被转换
107
+ * 2. 所有以 'Id' 或 '_id' 结尾的字段会被自动转换
108
+ * 3. 所有以 'At' 或 '_at' 结尾的字段会被自动转换(时间戳字段)
109
+ * 4. 其他字段保持不变
110
+ */
111
+ private convertBigIntFields<T = any>(arr: Record<string, any>[], fields: string[] = ['id', 'pid', 'sort']): T[] {
112
+ if (!arr || !Array.isArray(arr)) return arr as T[];
113
+
114
+ return arr.map((item) => {
115
+ const converted = { ...item };
116
+
117
+ // 遍历对象的所有字段
118
+ for (const [key, value] of Object.entries(converted)) {
119
+ // 跳过 undefined 和 null
120
+ if (value === undefined || value === null) {
121
+ continue;
122
+ }
123
+
124
+ // 判断是否需要转换:
125
+ // 1. 在白名单中
126
+ // 2. 以 'Id' 结尾(如 userId, roleId, categoryId)
127
+ // 3. 以 '_id' 结尾(如 user_id, role_id)
128
+ // 4. 以 'At' 结尾(如 createdAt, updatedAt)
129
+ // 5. 以 '_at' 结尾(如 created_at, updated_at)
130
+ const shouldConvert = fields.includes(key) || key.endsWith('Id') || key.endsWith('_id') || key.endsWith('At') || key.endsWith('_at');
131
+
132
+ if (shouldConvert && typeof value === 'string') {
133
+ const num = Number(value);
134
+ if (!isNaN(num)) {
135
+ converted[key] = num;
136
+ }
137
+ }
138
+ // number 类型保持不变(小于 u32 的值)
139
+ }
140
+
141
+ return converted as T;
142
+ }) as T[];
143
+ }
144
+
145
+ /**
146
+ * Where 条件键名转下划线格式(递归处理嵌套)(私有方法)
147
+ * 支持操作符字段(如 userId$gt)和逻辑操作符($or, $and)
148
+ */
149
+ private whereKeysToSnake(where: any): any {
150
+ if (!where || typeof where !== 'object') return where;
151
+
152
+ // 处理数组($or, $and 等)
153
+ if (Array.isArray(where)) {
154
+ return where.map((item) => this.whereKeysToSnake(item));
155
+ }
156
+
157
+ const result: any = {};
158
+ for (const [key, value] of Object.entries(where)) {
159
+ // 保留 $or, $and 等逻辑操作符
160
+ if (key === '$or' || key === '$and') {
161
+ result[key] = (value as any[]).map((item) => this.whereKeysToSnake(item));
162
+ continue;
163
+ }
164
+
165
+ // 处理带操作符的字段名(如 userId$gt)
166
+ if (key.includes('$')) {
167
+ const lastDollarIndex = key.lastIndexOf('$');
168
+ const fieldName = key.substring(0, lastDollarIndex);
169
+ const operator = key.substring(lastDollarIndex);
170
+ const snakeKey = snakeCase(fieldName) + operator;
171
+ result[snakeKey] = value;
172
+ continue;
173
+ }
174
+
175
+ // 普通字段:转换键名,递归处理值(支持嵌套对象)
176
+ const snakeKey = snakeCase(key);
177
+ if (typeof value === 'object' && value !== null && !Array.isArray(value)) {
178
+ result[snakeKey] = this.whereKeysToSnake(value);
179
+ } else {
180
+ result[snakeKey] = value;
181
+ }
182
+ }
183
+
184
+ return result;
185
+ }
186
+
141
187
  /**
142
188
  * 执行 SQL(使用 sql.unsafe,带慢查询日志)
143
189
  */
@@ -176,7 +222,7 @@ export class DbHelper {
176
222
  */
177
223
  async tableExists(tableName: string): Promise<boolean> {
178
224
  // 将表名转换为下划线格式
179
- const snakeTableName = toSnakeCase(tableName);
225
+ const snakeTableName = snakeCase(tableName);
180
226
 
181
227
  const result = await this.executeWithConn('SELECT COUNT(*) as count FROM information_schema.TABLES WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ?', [snakeTableName]);
182
228
 
@@ -228,7 +274,7 @@ export class DbHelper {
228
274
  const camelRow = keysToCamel<T>(row);
229
275
 
230
276
  // 转换 BIGINT 字段(id, pid 等)为数字类型
231
- return convertBigIntFields<T>([camelRow])[0];
277
+ return this.convertBigIntFields<T>([camelRow])[0];
232
278
  }
233
279
 
234
280
  /**
@@ -288,7 +334,7 @@ export class DbHelper {
288
334
 
289
335
  // 转换 BIGINT 字段(id, pid 等)为数字类型
290
336
  return {
291
- lists: convertBigIntFields<T>(camelList),
337
+ lists: this.convertBigIntFields<T>(camelList),
292
338
  total: total,
293
339
  page: prepared.page,
294
340
  limit: prepared.limit,
@@ -335,7 +381,7 @@ export class DbHelper {
335
381
  const camelResult = arrayKeysToCamel<T>(result);
336
382
 
337
383
  // 转换 BIGINT 字段(id, pid 等)为数字类型
338
- return convertBigIntFields<T>(camelResult);
384
+ return this.convertBigIntFields<T>(camelResult);
339
385
  }
340
386
 
341
387
  /**
@@ -349,7 +395,7 @@ export class DbHelper {
349
395
  const cleanData = this.cleanFields(data);
350
396
 
351
397
  // 转换表名:小驼峰 → 下划线
352
- const snakeTable = toSnakeCase(table);
398
+ const snakeTable = snakeCase(table);
353
399
 
354
400
  // 处理数据(自动添加必要字段)
355
401
  // 字段名转换:小驼峰 → 下划线
@@ -405,7 +451,7 @@ export class DbHelper {
405
451
  }
406
452
 
407
453
  // 转换表名:小驼峰 → 下划线
408
- const snakeTable = toSnakeCase(table);
454
+ const snakeTable = snakeCase(table);
409
455
 
410
456
  // 批量生成 ID(一次性从 Redis 获取 N 个 ID)
411
457
  const ids = await this.befly.redis.genTimeIDBatch(dataList.length);
@@ -459,7 +505,7 @@ export class DbHelper {
459
505
  const cleanWhere = this.cleanFields(where);
460
506
 
461
507
  // 转换表名:小驼峰 → 下划线
462
- const snakeTable = toSnakeCase(table);
508
+ const snakeTable = snakeCase(table);
463
509
 
464
510
  // 字段名转换:小驼峰 → 下划线
465
511
  const snakeData = keysToSnake(cleanData);
@@ -507,7 +553,7 @@ export class DbHelper {
507
553
  const { table, where } = options;
508
554
 
509
555
  // 转换表名:小驼峰 → 下划线
510
- const snakeTable = toSnakeCase(table);
556
+ const snakeTable = snakeCase(table);
511
557
 
512
558
  // 清理条件字段
513
559
  const cleanWhere = this.cleanFields(where);
@@ -666,8 +712,8 @@ export class DbHelper {
666
712
  */
667
713
  async increment(table: string, field: string, where: WhereConditions, value: number = 1): Promise<number> {
668
714
  // 转换表名和字段名:小驼峰 → 下划线
669
- const snakeTable = toSnakeCase(table);
670
- const snakeField = toSnakeCase(field);
715
+ const snakeTable = snakeCase(table);
716
+ const snakeField = snakeCase(field);
671
717
 
672
718
  // 验证表名格式(只允许字母、数字、下划线)
673
719
  if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(snakeTable)) {