befly 3.10.18 → 3.11.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.
Files changed (223) hide show
  1. package/README.md +83 -307
  2. package/dist/befly.config.d.ts +7 -0
  3. package/{befly.config.ts → dist/befly.config.js} +11 -36
  4. package/dist/befly.js +15621 -0
  5. package/dist/befly.min.js +21 -0
  6. package/dist/checks/checkApi.d.ts +1 -0
  7. package/{checks/checkApi.ts → dist/checks/checkApi.js} +12 -30
  8. package/dist/checks/checkHook.d.ts +1 -0
  9. package/dist/checks/checkHook.js +86 -0
  10. package/dist/checks/checkMenu.d.ts +7 -0
  11. package/{checks/checkMenu.ts → dist/checks/checkMenu.js} +18 -53
  12. package/dist/checks/checkPlugin.d.ts +1 -0
  13. package/dist/checks/checkPlugin.js +86 -0
  14. package/dist/checks/checkTable.d.ts +6 -0
  15. package/{checks/checkTable.ts → dist/checks/checkTable.js} +17 -41
  16. package/dist/configs/presetFields.d.ts +4 -0
  17. package/{configs/presetFields.ts → dist/configs/presetFields.js} +1 -1
  18. package/dist/configs/presetRegexp.d.ts +145 -0
  19. package/{utils/regex.ts → dist/configs/presetRegexp.js} +8 -31
  20. package/dist/hooks/auth.d.ts +7 -0
  21. package/{hooks/auth.ts → dist/hooks/auth.js} +8 -10
  22. package/dist/hooks/cors.d.ts +11 -0
  23. package/{hooks/cors.ts → dist/hooks/cors.js} +5 -13
  24. package/dist/hooks/parser.d.ts +14 -0
  25. package/{hooks/parser.ts → dist/hooks/parser.js} +31 -45
  26. package/dist/hooks/permission.d.ts +14 -0
  27. package/{hooks/permission.ts → dist/hooks/permission.js} +16 -25
  28. package/dist/hooks/validator.d.ts +11 -0
  29. package/{hooks/validator.ts → dist/hooks/validator.js} +9 -14
  30. package/dist/index.d.ts +26 -0
  31. package/{main.ts → dist/index.js} +61 -100
  32. package/dist/lib/asyncContext.d.ts +21 -0
  33. package/dist/lib/asyncContext.js +27 -0
  34. package/dist/lib/cacheHelper.d.ts +95 -0
  35. package/{lib/cacheHelper.ts → dist/lib/cacheHelper.js} +45 -105
  36. package/dist/lib/cacheKeys.d.ts +23 -0
  37. package/{lib/cacheKeys.ts → dist/lib/cacheKeys.js} +5 -10
  38. package/dist/lib/cipher.d.ts +153 -0
  39. package/{lib/cipher.ts → dist/lib/cipher.js} +23 -44
  40. package/dist/lib/connect.d.ts +91 -0
  41. package/{lib/connect.ts → dist/lib/connect.js} +47 -88
  42. package/dist/lib/dbDialect.d.ts +87 -0
  43. package/{lib/dbDialect.ts → dist/lib/dbDialect.js} +32 -112
  44. package/dist/lib/dbHelper.d.ts +204 -0
  45. package/{lib/dbHelper.ts → dist/lib/dbHelper.js} +82 -241
  46. package/dist/lib/dbUtils.d.ts +68 -0
  47. package/{lib/dbUtils.ts → dist/lib/dbUtils.js} +51 -126
  48. package/dist/lib/jwt.d.ts +13 -0
  49. package/{lib/jwt.ts → dist/lib/jwt.js} +11 -32
  50. package/dist/lib/logger.d.ts +42 -0
  51. package/dist/lib/logger.js +1144 -0
  52. package/dist/lib/redisHelper.d.ts +185 -0
  53. package/{lib/redisHelper.ts → dist/lib/redisHelper.js} +97 -141
  54. package/dist/lib/sqlBuilder.d.ts +160 -0
  55. package/{lib/sqlBuilder.ts → dist/lib/sqlBuilder.js} +132 -278
  56. package/dist/lib/sqlCheck.d.ts +23 -0
  57. package/{lib/sqlCheck.ts → dist/lib/sqlCheck.js} +24 -41
  58. package/dist/lib/validator.d.ts +45 -0
  59. package/{lib/validator.ts → dist/lib/validator.js} +44 -61
  60. package/dist/loader/loadApis.d.ts +12 -0
  61. package/{loader/loadApis.ts → dist/loader/loadApis.js} +10 -20
  62. package/dist/loader/loadHooks.d.ts +7 -0
  63. package/dist/loader/loadHooks.js +35 -0
  64. package/dist/loader/loadPlugins.d.ts +8 -0
  65. package/{loader/loadPlugins.ts → dist/loader/loadPlugins.js} +14 -26
  66. package/dist/paths.d.ts +93 -0
  67. package/{paths.ts → dist/paths.js} +6 -19
  68. package/dist/plugins/cache.d.ts +16 -0
  69. package/{plugins/cache.ts → dist/plugins/cache.js} +7 -12
  70. package/dist/plugins/cipher.d.ts +12 -0
  71. package/{plugins/cipher.ts → dist/plugins/cipher.js} +4 -6
  72. package/dist/plugins/config.d.ts +12 -0
  73. package/dist/plugins/config.js +8 -0
  74. package/dist/plugins/db.d.ts +16 -0
  75. package/{plugins/db.ts → dist/plugins/db.js} +11 -17
  76. package/dist/plugins/jwt.d.ts +12 -0
  77. package/dist/plugins/jwt.js +12 -0
  78. package/dist/plugins/logger.d.ts +32 -0
  79. package/{plugins/logger.ts → dist/plugins/logger.js} +5 -8
  80. package/dist/plugins/redis.d.ts +16 -0
  81. package/{plugins/redis.ts → dist/plugins/redis.js} +9 -12
  82. package/dist/plugins/tool.d.ts +81 -0
  83. package/{plugins/tool.ts → dist/plugins/tool.js} +9 -30
  84. package/dist/router/api.d.ts +14 -0
  85. package/dist/router/api.js +107 -0
  86. package/dist/router/static.d.ts +9 -0
  87. package/{router/static.ts → dist/router/static.js} +20 -34
  88. package/dist/scripts/ensureDist.d.ts +1 -0
  89. package/dist/scripts/ensureDist.js +296 -0
  90. package/dist/sync/syncApi.d.ts +3 -0
  91. package/{sync/syncApi.ts → dist/sync/syncApi.js} +35 -55
  92. package/dist/sync/syncCache.d.ts +2 -0
  93. package/{sync/syncCache.ts → dist/sync/syncCache.js} +1 -6
  94. package/dist/sync/syncDev.d.ts +6 -0
  95. package/{sync/syncDev.ts → dist/sync/syncDev.js} +29 -62
  96. package/dist/sync/syncMenu.d.ts +14 -0
  97. package/{sync/syncMenu.ts → dist/sync/syncMenu.js} +65 -125
  98. package/dist/sync/syncTable.d.ts +151 -0
  99. package/{sync/syncTable.ts → dist/sync/syncTable.js} +172 -379
  100. package/{types → dist/types}/api.d.ts +12 -51
  101. package/dist/types/api.js +4 -0
  102. package/{types → dist/types}/befly.d.ts +32 -227
  103. package/dist/types/befly.js +4 -0
  104. package/{types → dist/types}/cache.d.ts +7 -15
  105. package/dist/types/cache.js +4 -0
  106. package/dist/types/cipher.d.ts +27 -0
  107. package/dist/types/cipher.js +7 -0
  108. package/{types → dist/types}/common.d.ts +8 -33
  109. package/dist/types/common.js +5 -0
  110. package/{types → dist/types}/context.d.ts +3 -5
  111. package/dist/types/context.js +4 -0
  112. package/{types → dist/types}/crypto.d.ts +0 -3
  113. package/dist/types/crypto.js +4 -0
  114. package/dist/types/database.d.ts +138 -0
  115. package/dist/types/database.js +4 -0
  116. package/dist/types/hook.d.ts +17 -0
  117. package/dist/types/hook.js +6 -0
  118. package/dist/types/jwt.d.ts +75 -0
  119. package/dist/types/jwt.js +4 -0
  120. package/dist/types/logger.d.ts +59 -0
  121. package/dist/types/logger.js +6 -0
  122. package/dist/types/plugin.d.ts +16 -0
  123. package/dist/types/plugin.js +6 -0
  124. package/dist/types/redis.d.ts +71 -0
  125. package/dist/types/redis.js +4 -0
  126. package/{types/roleApisCache.ts → dist/types/roleApisCache.d.ts} +0 -2
  127. package/dist/types/roleApisCache.js +8 -0
  128. package/dist/types/sync.d.ts +92 -0
  129. package/dist/types/sync.js +4 -0
  130. package/dist/types/table.d.ts +34 -0
  131. package/dist/types/table.js +4 -0
  132. package/dist/types/validate.d.ts +67 -0
  133. package/dist/types/validate.js +4 -0
  134. package/dist/utils/calcPerfTime.d.ts +4 -0
  135. package/{utils/calcPerfTime.ts → dist/utils/calcPerfTime.js} +3 -3
  136. package/dist/utils/convertBigIntFields.d.ts +11 -0
  137. package/{utils/convertBigIntFields.ts → dist/utils/convertBigIntFields.js} +5 -9
  138. package/dist/utils/cors.d.ts +8 -0
  139. package/{utils/cors.ts → dist/utils/cors.js} +1 -3
  140. package/dist/utils/disableMenusGlob.d.ts +13 -0
  141. package/{utils/disableMenusGlob.ts → dist/utils/disableMenusGlob.js} +9 -29
  142. package/dist/utils/fieldClear.d.ts +11 -0
  143. package/{utils/fieldClear.ts → dist/utils/fieldClear.js} +15 -33
  144. package/dist/utils/getClientIp.d.ts +6 -0
  145. package/{utils/getClientIp.ts → dist/utils/getClientIp.js} +1 -7
  146. package/dist/utils/importDefault.d.ts +1 -0
  147. package/dist/utils/importDefault.js +29 -0
  148. package/dist/utils/isDirentDirectory.d.ts +2 -0
  149. package/{utils/isDirentDirectory.ts → dist/utils/isDirentDirectory.js} +3 -8
  150. package/dist/utils/loadMenuConfigs.d.ts +29 -0
  151. package/{utils/loadMenuConfigs.ts → dist/utils/loadMenuConfigs.js} +66 -52
  152. package/dist/utils/mergeAndConcat.d.ts +7 -0
  153. package/dist/utils/mergeAndConcat.js +72 -0
  154. package/dist/utils/processAtSymbol.d.ts +4 -0
  155. package/{utils/processFields.ts → dist/utils/processAtSymbol.js} +5 -9
  156. package/dist/utils/processInfo.d.ts +24 -0
  157. package/{utils/process.ts → dist/utils/processInfo.js} +2 -18
  158. package/dist/utils/response.d.ts +20 -0
  159. package/{utils/response.ts → dist/utils/response.js} +28 -49
  160. package/dist/utils/scanAddons.d.ts +17 -0
  161. package/{utils/scanAddons.ts → dist/utils/scanAddons.js} +7 -41
  162. package/dist/utils/scanConfig.d.ts +26 -0
  163. package/{utils/scanConfig.ts → dist/utils/scanConfig.js} +28 -66
  164. package/dist/utils/scanCoreBuiltins.d.ts +3 -0
  165. package/dist/utils/scanCoreBuiltins.js +65 -0
  166. package/dist/utils/scanFiles.d.ts +30 -0
  167. package/{utils/scanFiles.ts → dist/utils/scanFiles.js} +44 -71
  168. package/dist/utils/scanSources.d.ts +10 -0
  169. package/dist/utils/scanSources.js +46 -0
  170. package/dist/utils/sortModules.d.ts +28 -0
  171. package/{utils/sortModules.ts → dist/utils/sortModules.js} +26 -66
  172. package/dist/utils/util.d.ts +84 -0
  173. package/dist/utils/util.js +262 -0
  174. package/package.json +26 -34
  175. package/.gitignore +0 -0
  176. package/bunfig.toml +0 -3
  177. package/checks/checkHook.ts +0 -48
  178. package/checks/checkPlugin.ts +0 -48
  179. package/configs/presetRegexp.ts +0 -225
  180. package/docs/README.md +0 -98
  181. package/docs/api/api.md +0 -1921
  182. package/docs/guide/examples.md +0 -926
  183. package/docs/guide/quickstart.md +0 -354
  184. package/docs/hooks/auth.md +0 -38
  185. package/docs/hooks/cors.md +0 -28
  186. package/docs/hooks/hook.md +0 -838
  187. package/docs/hooks/parser.md +0 -19
  188. package/docs/hooks/rateLimit.md +0 -47
  189. package/docs/infra/redis.md +0 -628
  190. package/docs/plugins/cipher.md +0 -61
  191. package/docs/plugins/database.md +0 -189
  192. package/docs/plugins/plugin.md +0 -986
  193. package/docs/reference/addon.md +0 -510
  194. package/docs/reference/config.md +0 -573
  195. package/docs/reference/logger.md +0 -495
  196. package/docs/reference/sync.md +0 -478
  197. package/docs/reference/table.md +0 -763
  198. package/docs/reference/validator.md +0 -620
  199. package/lib/asyncContext.ts +0 -43
  200. package/lib/logger.ts +0 -811
  201. package/loader/loadHooks.ts +0 -51
  202. package/plugins/config.ts +0 -13
  203. package/plugins/jwt.ts +0 -15
  204. package/router/api.ts +0 -130
  205. package/tsconfig.json +0 -8
  206. package/types/database.d.ts +0 -541
  207. package/types/hook.d.ts +0 -25
  208. package/types/jwt.d.ts +0 -118
  209. package/types/logger.d.ts +0 -65
  210. package/types/plugin.d.ts +0 -19
  211. package/types/redis.d.ts +0 -83
  212. package/types/sync.d.ts +0 -398
  213. package/types/table.d.ts +0 -216
  214. package/types/validate.d.ts +0 -69
  215. package/utils/arrayKeysToCamel.ts +0 -18
  216. package/utils/configTypes.ts +0 -3
  217. package/utils/genShortId.ts +0 -12
  218. package/utils/importDefault.ts +0 -21
  219. package/utils/keysToCamel.ts +0 -22
  220. package/utils/keysToSnake.ts +0 -22
  221. package/utils/pickFields.ts +0 -19
  222. package/utils/scanSources.ts +0 -64
  223. package/utils/sqlLog.ts +0 -37
@@ -2,108 +2,78 @@
2
2
  * 数据库助手 - TypeScript 版本
3
3
  * 提供数据库 CRUD 操作的封装
4
4
  */
5
-
6
- import type { WhereConditions, JoinOption } from "../types/common.ts";
7
- import type { QueryOptions, InsertOptions, UpdateOptions, DeleteOptions, ListResult, AllResult, TransactionCallback, DbResult, SqlInfo, ListSql } from "../types/database.ts";
8
- import type { DbDialect } from "./dbDialect.ts";
9
-
10
- import { snakeCase } from "es-toolkit/string";
11
-
12
- import { arrayKeysToCamel } from "../utils/arrayKeysToCamel.ts";
13
- import { convertBigIntFields } from "../utils/convertBigIntFields.ts";
14
- import { fieldClear } from "../utils/fieldClear.ts";
15
- import { keysToCamel } from "../utils/keysToCamel.ts";
16
- import { CacheKeys } from "./cacheKeys.ts";
17
- import { MySqlDialect } from "./dbDialect.ts";
18
- import { DbUtils } from "./dbUtils.ts";
19
- import { Logger } from "./logger.ts";
20
- import { SqlBuilder } from "./sqlBuilder.ts";
21
- import { SqlCheck } from "./sqlCheck.ts";
22
-
5
+ import { convertBigIntFields } from "../utils/convertBigIntFields";
6
+ import { fieldClear } from "../utils/fieldClear";
7
+ import { arrayKeysToCamel, keysToCamel, snakeCase } from "../utils/util";
8
+ import { CacheKeys } from "./cacheKeys";
9
+ import { MySqlDialect } from "./dbDialect";
10
+ import { DbUtils } from "./dbUtils";
11
+ import { Logger } from "./logger";
12
+ import { SqlBuilder } from "./sqlBuilder";
13
+ import { SqlCheck } from "./sqlCheck";
23
14
  const TABLE_COLUMNS_CACHE_TTL_SECONDS = 3600;
24
-
25
- type RedisCacheLike = {
26
- getObject<T = any>(key: string): Promise<T | null>;
27
- setObject<T = any>(key: string, obj: T, ttl?: number | null): Promise<string | null>;
28
- genTimeID(): Promise<number>;
29
- };
30
-
31
15
  /**
32
16
  * 数据库助手类
33
17
  */
34
18
  export class DbHelper {
35
- private redis: RedisCacheLike;
36
- private dialect: DbDialect;
37
- private sql: any = null;
38
- private isTransaction: boolean = false;
39
-
19
+ redis;
20
+ dialect;
21
+ sql = null;
22
+ isTransaction = false;
40
23
  /**
41
24
  * 构造函数
42
25
  * @param redis - Redis 实例
43
26
  * @param sql - Bun SQL 客户端(可选,用于事务)
44
27
  */
45
- constructor(options: { redis: RedisCacheLike; sql?: any | null; dialect?: DbDialect }) {
28
+ constructor(options) {
46
29
  this.redis = options.redis;
47
30
  this.sql = options.sql || null;
48
31
  this.isTransaction = !!options.sql;
49
-
50
32
  // 默认使用 MySQL 方言(当前 core 的表结构/语法也主要基于 MySQL)
51
33
  this.dialect = options.dialect ? options.dialect : new MySqlDialect();
52
34
  }
53
-
54
- private createSqlBuilder(): SqlBuilder {
35
+ createSqlBuilder() {
55
36
  return new SqlBuilder({ quoteIdent: this.dialect.quoteIdent.bind(this.dialect) });
56
37
  }
57
-
58
38
  /**
59
39
  * 获取表的所有字段名(Redis 缓存)
60
40
  * @param table - 表名(下划线格式)
61
41
  * @returns 字段名数组(下划线格式)
62
42
  */
63
- private async getTableColumns(table: string): Promise<string[]> {
43
+ async getTableColumns(table) {
64
44
  // 1. 先查 Redis 缓存
65
45
  const cacheKey = CacheKeys.tableColumns(table);
66
- const columns = await this.redis.getObject<string[]>(cacheKey);
67
-
46
+ const columns = await this.redis.getObject(cacheKey);
68
47
  if (columns && columns.length > 0) {
69
48
  return columns;
70
49
  }
71
-
72
50
  // 2. 缓存未命中,查询数据库
73
51
  const query = this.dialect.getTableColumnsQuery(table);
74
52
  const execRes = await this.executeWithConn(query.sql, query.params);
75
53
  const result = execRes.data;
76
-
77
54
  if (!result || result.length === 0) {
78
55
  throw new Error(`表 ${table} 不存在或没有字段`);
79
56
  }
80
-
81
57
  const columnNames = this.dialect.getTableColumnsFromResult(result);
82
-
83
58
  // 3. 写入 Redis 缓存
84
59
  const cacheRes = await this.redis.setObject(cacheKey, columnNames, TABLE_COLUMNS_CACHE_TTL_SECONDS);
85
60
  if (cacheRes === null) {
86
61
  Logger.warn({ table: table, cacheKey: cacheKey }, "表字段缓存写入 Redis 失败");
87
62
  }
88
-
89
63
  return columnNames;
90
64
  }
91
-
92
65
  /**
93
66
  * 统一的查询参数预处理方法
94
67
  */
95
- private async prepareQueryOptions(options: QueryOptions) {
68
+ async prepareQueryOptions(options) {
96
69
  const cleanWhere = fieldClear(options.where || {}, { excludeValues: [null, undefined] });
97
70
  const hasJoins = options.joins && options.joins.length > 0;
98
-
99
71
  // 联查时使用特殊处理逻辑
100
72
  if (hasJoins) {
101
73
  // 联查时字段直接处理(支持表名.字段名格式)
102
74
  const processedFields = (options.fields || []).map((f) => DbUtils.processJoinField(f));
103
-
104
75
  const normalizedTableRef = DbUtils.normalizeTableRef(options.table);
105
76
  const mainQualifier = DbUtils.getJoinMainQualifier(options.table);
106
-
107
77
  return {
108
78
  table: normalizedTableRef,
109
79
  tableQualifier: mainQualifier,
@@ -115,10 +85,8 @@ export class DbHelper {
115
85
  limit: options.limit || 10
116
86
  };
117
87
  }
118
-
119
88
  // 单表查询使用原有逻辑
120
89
  const processedFields = await DbUtils.fieldsToSnake(snakeCase(options.table), options.fields || [], this.getTableColumns.bind(this));
121
-
122
90
  return {
123
91
  table: snakeCase(options.table),
124
92
  tableQualifier: snakeCase(options.table),
@@ -130,17 +98,15 @@ export class DbHelper {
130
98
  limit: options.limit || 10
131
99
  };
132
100
  }
133
-
134
101
  /**
135
102
  * 为 builder 添加 JOIN
136
103
  */
137
- private applyJoins(builder: SqlBuilder, joins?: JoinOption[]): void {
138
- if (!joins || joins.length === 0) return;
139
-
104
+ applyJoins(builder, joins) {
105
+ if (!joins || joins.length === 0)
106
+ return;
140
107
  for (const join of joins) {
141
108
  const processedTable = DbUtils.normalizeTableRef(join.table);
142
109
  const type = join.type || "left";
143
-
144
110
  switch (type) {
145
111
  case "inner":
146
112
  builder.innerJoin(processedTable, join.on);
@@ -161,47 +127,41 @@ export class DbHelper {
161
127
  * - DbHelper 不再负责打印 SQL 调试日志
162
128
  * - SQL 信息由调用方基于返回值中的 sql 自行输出
163
129
  */
164
- private async executeWithConn(sqlStr: string, params?: any[]): Promise<DbResult<any>> {
130
+ async executeWithConn(sqlStr, params) {
165
131
  if (!this.sql) {
166
132
  throw new Error("数据库连接未初始化");
167
133
  }
168
-
169
134
  // 强制类型检查:只接受字符串类型的 SQL
170
135
  if (typeof sqlStr !== "string") {
171
136
  throw new Error(`executeWithConn 只接受字符串类型的 SQL,收到类型: ${typeof sqlStr},值: ${JSON.stringify(sqlStr)}`);
172
137
  }
173
-
174
138
  // 记录开始时间
175
139
  const startTime = Date.now();
176
-
177
140
  const safeParams = Array.isArray(params) ? params : [];
178
-
179
141
  try {
180
142
  // 使用 sql.unsafe 执行查询
181
143
  let result;
182
144
  if (safeParams.length > 0) {
183
145
  result = await this.sql.unsafe(sqlStr, safeParams);
184
- } else {
146
+ }
147
+ else {
185
148
  result = await this.sql.unsafe(sqlStr);
186
149
  }
187
-
188
150
  // 计算执行时间
189
151
  const duration = Date.now() - startTime;
190
-
191
- const sql: SqlInfo = {
152
+ const sql = {
192
153
  sql: sqlStr,
193
154
  params: safeParams,
194
155
  duration: duration
195
156
  };
196
-
197
157
  return {
198
158
  data: result,
199
159
  sql: sql
200
160
  };
201
- } catch (error: any) {
161
+ }
162
+ catch (error) {
202
163
  const duration = Date.now() - startTime;
203
-
204
- const enhancedError: any = new Error(`SQL执行失败: ${error.message}`);
164
+ const enhancedError = new Error(`SQL执行失败: ${error.message}`);
205
165
  enhancedError.originalError = error;
206
166
  enhancedError.params = safeParams;
207
167
  enhancedError.duration = duration;
@@ -213,36 +173,31 @@ export class DbHelper {
213
173
  throw enhancedError;
214
174
  }
215
175
  }
216
-
217
176
  /**
218
177
  * 执行原生 SQL(内部工具/同步脚本专用)
219
178
  *
220
179
  * - 复用当前 DbHelper 持有的连接/事务
221
180
  * - 统一走 executeWithConn,保持参数校验与错误行为一致
222
181
  */
223
- public async unsafe(sqlStr: string, params?: any[]): Promise<DbResult<any>> {
182
+ async unsafe(sqlStr, params) {
224
183
  return await this.executeWithConn(sqlStr, params);
225
184
  }
226
-
227
185
  /**
228
186
  * 检查表是否存在
229
187
  * @param tableName - 表名(支持小驼峰,会自动转换为下划线)
230
188
  * @returns 表是否存在
231
189
  */
232
- async tableExists(tableName: string): Promise<DbResult<boolean>> {
190
+ async tableExists(tableName) {
233
191
  // 将表名转换为下划线格式
234
192
  const snakeTableName = snakeCase(tableName);
235
-
236
193
  const query = this.dialect.tableExistsQuery(snakeTableName);
237
194
  const execRes = await this.executeWithConn(query.sql, query.params);
238
195
  const exists = (execRes.data?.[0]?.count || 0) > 0;
239
-
240
196
  return {
241
197
  data: exists,
242
198
  sql: execRes.sql
243
199
  };
244
200
  }
245
-
246
201
  /**
247
202
  * 查询记录数
248
203
  * @param options.table - 表名(支持小驼峰或下划线格式,会自动转换)
@@ -260,27 +215,22 @@ export class DbHelper {
260
215
  * where: { 'o.state': 1 }
261
216
  * });
262
217
  */
263
- async getCount(options: Omit<QueryOptions, "fields" | "page" | "limit" | "orderBy">): Promise<DbResult<number>> {
264
- const { table, where, joins, tableQualifier } = await this.prepareQueryOptions(options as QueryOptions);
265
-
218
+ async getCount(options) {
219
+ const { table, where, joins, tableQualifier } = await this.prepareQueryOptions(options);
266
220
  const builder = this.createSqlBuilder()
267
221
  .selectRaw("COUNT(*) as count")
268
222
  .from(table)
269
223
  .where(DbUtils.addDefaultStateFilter(where, tableQualifier, !!joins));
270
-
271
224
  // 添加 JOIN
272
225
  this.applyJoins(builder, joins);
273
-
274
226
  const { sql, params } = builder.toSelectSql();
275
227
  const execRes = await this.executeWithConn(sql, params);
276
228
  const count = execRes.data?.[0]?.count || 0;
277
-
278
229
  return {
279
230
  data: count,
280
231
  sql: execRes.sql
281
232
  };
282
233
  }
283
-
284
234
  /**
285
235
  * 查询单条数据
286
236
  * @param options.table - 表名(支持小驼峰或下划线格式,会自动转换;联查时可带别名如 'order o')
@@ -297,21 +247,17 @@ export class DbHelper {
297
247
  * where: { 'o.id': 1 }
298
248
  * })
299
249
  */
300
- async getOne<T extends Record<string, any> = Record<string, any>>(options: QueryOptions): Promise<DbResult<T | null>> {
250
+ async getOne(options) {
301
251
  const { table, fields, where, joins, tableQualifier } = await this.prepareQueryOptions(options);
302
-
303
252
  const builder = this.createSqlBuilder()
304
253
  .select(fields)
305
254
  .from(table)
306
255
  .where(DbUtils.addDefaultStateFilter(where, tableQualifier, !!joins));
307
-
308
256
  // 添加 JOIN
309
257
  this.applyJoins(builder, joins);
310
-
311
258
  const { sql, params } = builder.toSelectSql();
312
259
  const execRes = await this.executeWithConn(sql, params);
313
260
  const result = execRes.data;
314
-
315
261
  // 字段名转换:下划线 → 小驼峰
316
262
  const row = result?.[0] || null;
317
263
  if (!row) {
@@ -320,26 +266,22 @@ export class DbHelper {
320
266
  sql: execRes.sql
321
267
  };
322
268
  }
323
-
324
- const camelRow = keysToCamel<T>(row);
325
-
269
+ const camelRow = keysToCamel(row);
326
270
  // 反序列化数组字段(JSON 字符串 → 数组)
327
- const deserialized = DbUtils.deserializeArrayFields<T>(camelRow);
271
+ const deserialized = DbUtils.deserializeArrayFields(camelRow);
328
272
  if (!deserialized) {
329
273
  return {
330
274
  data: null,
331
275
  sql: execRes.sql
332
276
  };
333
277
  }
334
-
335
278
  // 转换 BIGINT 字段(id, pid 等)为数字类型
336
- const data = convertBigIntFields<T>([deserialized])[0];
279
+ const data = convertBigIntFields([deserialized])[0];
337
280
  return {
338
281
  data: data,
339
282
  sql: execRes.sql
340
283
  };
341
284
  }
342
-
343
285
  /**
344
286
  * 查询列表(带分页)
345
287
  * @param options.table - 表名(支持小驼峰或下划线格式,会自动转换;联查时可带别名)
@@ -362,9 +304,8 @@ export class DbHelper {
362
304
  * limit: 10
363
305
  * })
364
306
  */
365
- async getList<T extends Record<string, any> = Record<string, any>>(options: QueryOptions): Promise<DbResult<ListResult<T>, ListSql>> {
307
+ async getList(options) {
366
308
  const prepared = await this.prepareQueryOptions(options);
367
-
368
309
  // 参数上限校验
369
310
  if (prepared.page < 1 || prepared.page > 10000) {
370
311
  throw new Error(`页码必须在 1 到 10000 之间 (table: ${options.table}, page: ${prepared.page}, limit: ${prepared.limit})`);
@@ -372,20 +313,15 @@ export class DbHelper {
372
313
  if (prepared.limit < 1 || prepared.limit > 1000) {
373
314
  throw new Error(`每页数量必须在 1 到 1000 之间 (table: ${options.table}, page: ${prepared.page}, limit: ${prepared.limit})`);
374
315
  }
375
-
376
316
  // 构建查询
377
317
  const whereFiltered = DbUtils.addDefaultStateFilter(prepared.where, prepared.tableQualifier, !!prepared.joins);
378
-
379
318
  // 查询总数
380
319
  const countBuilder = this.createSqlBuilder().selectRaw("COUNT(*) as total").from(prepared.table).where(whereFiltered);
381
-
382
320
  // 添加 JOIN(计数也需要)
383
321
  this.applyJoins(countBuilder, prepared.joins);
384
-
385
322
  const { sql: countSql, params: countParams } = countBuilder.toSelectSql();
386
323
  const countExecRes = await this.executeWithConn(countSql, countParams);
387
324
  const total = countExecRes.data?.[0]?.total || 0;
388
-
389
325
  // 如果总数为 0,直接返回,不执行第二次查询
390
326
  if (total === 0) {
391
327
  return {
@@ -401,33 +337,26 @@ export class DbHelper {
401
337
  }
402
338
  };
403
339
  }
404
-
405
340
  // 查询数据
406
341
  const offset = (prepared.page - 1) * prepared.limit;
407
342
  const dataBuilder = this.createSqlBuilder().select(prepared.fields).from(prepared.table).where(whereFiltered).limit(prepared.limit).offset(offset);
408
-
409
343
  // 添加 JOIN
410
344
  this.applyJoins(dataBuilder, prepared.joins);
411
-
412
345
  // 只有用户明确指定了 orderBy 才添加排序
413
346
  if (prepared.orderBy && prepared.orderBy.length > 0) {
414
347
  dataBuilder.orderBy(prepared.orderBy);
415
348
  }
416
-
417
349
  const { sql: dataSql, params: dataParams } = dataBuilder.toSelectSql();
418
350
  const dataExecRes = await this.executeWithConn(dataSql, dataParams);
419
351
  const list = dataExecRes.data || [];
420
-
421
352
  // 字段名转换:下划线 → 小驼峰
422
- const camelList = arrayKeysToCamel<T>(list);
423
-
353
+ const camelList = arrayKeysToCamel(list);
424
354
  // 反序列化数组字段
425
- const deserializedList = camelList.map((item) => DbUtils.deserializeArrayFields<T>(item)).filter((item): item is T => item !== null);
426
-
355
+ const deserializedList = camelList.map((item) => DbUtils.deserializeArrayFields(item)).filter((item) => item !== null);
427
356
  // 转换 BIGINT 字段(id, pid 等)为数字类型
428
357
  return {
429
358
  data: {
430
- lists: convertBigIntFields<T>(deserializedList),
359
+ lists: convertBigIntFields(deserializedList),
431
360
  total: total,
432
361
  page: prepared.page,
433
362
  limit: prepared.limit,
@@ -439,7 +368,6 @@ export class DbHelper {
439
368
  }
440
369
  };
441
370
  }
442
-
443
371
  /**
444
372
  * 查询所有数据(不分页,有上限保护)
445
373
  * @param options.table - 表名(支持小驼峰或下划线格式,会自动转换;联查时可带别名)
@@ -457,25 +385,19 @@ export class DbHelper {
457
385
  * where: { 'o.state': 1 }
458
386
  * })
459
387
  */
460
- async getAll<T extends Record<string, any> = Record<string, any>>(options: Omit<QueryOptions, "page" | "limit">): Promise<DbResult<AllResult<T>, ListSql>> {
388
+ async getAll(options) {
461
389
  // 添加硬性上限保护,防止内存溢出
462
390
  const MAX_LIMIT = 10000;
463
391
  const WARNING_LIMIT = 1000;
464
-
465
392
  const prepared = await this.prepareQueryOptions({ ...options, page: 1, limit: 10 });
466
-
467
393
  const whereFiltered = DbUtils.addDefaultStateFilter(prepared.where, prepared.tableQualifier, !!prepared.joins);
468
-
469
394
  // 查询真实总数
470
395
  const countBuilder = this.createSqlBuilder().selectRaw("COUNT(*) as total").from(prepared.table).where(whereFiltered);
471
-
472
396
  // 添加 JOIN(计数也需要)
473
397
  this.applyJoins(countBuilder, prepared.joins);
474
-
475
398
  const { sql: countSql, params: countParams } = countBuilder.toSelectSql();
476
399
  const countExecRes = await this.executeWithConn(countSql, countParams);
477
400
  const total = countExecRes.data?.[0]?.total || 0;
478
-
479
401
  // 如果总数为 0,直接返回
480
402
  if (total === 0) {
481
403
  return {
@@ -488,40 +410,30 @@ export class DbHelper {
488
410
  }
489
411
  };
490
412
  }
491
-
492
413
  // 查询数据(受上限保护)
493
414
  const dataBuilder = this.createSqlBuilder().select(prepared.fields).from(prepared.table).where(whereFiltered).limit(MAX_LIMIT);
494
-
495
415
  // 添加 JOIN
496
416
  this.applyJoins(dataBuilder, prepared.joins);
497
-
498
417
  if (prepared.orderBy && prepared.orderBy.length > 0) {
499
418
  dataBuilder.orderBy(prepared.orderBy);
500
419
  }
501
-
502
420
  const { sql: dataSql, params: dataParams } = dataBuilder.toSelectSql();
503
421
  const dataExecRes = await this.executeWithConn(dataSql, dataParams);
504
422
  const result = dataExecRes.data || [];
505
-
506
423
  // 警告日志:返回数据超过警告阈值
507
424
  if (result.length >= WARNING_LIMIT) {
508
425
  Logger.warn({ table: options.table, count: result.length, total: total }, "getAll 返回数据过多,建议使用 getList 分页查询");
509
426
  }
510
-
511
427
  // 如果达到上限,额外警告
512
428
  if (result.length >= MAX_LIMIT) {
513
429
  Logger.warn({ table: options.table, limit: MAX_LIMIT, total: total }, `getAll 达到最大限制 ${MAX_LIMIT},实际总数 ${total},只返回前 ${MAX_LIMIT} 条`);
514
430
  }
515
-
516
431
  // 字段名转换:下划线 → 小驼峰
517
- const camelResult = arrayKeysToCamel<T>(result);
518
-
432
+ const camelResult = arrayKeysToCamel(result);
519
433
  // 反序列化数组字段
520
- const deserializedList = camelResult.map((item) => DbUtils.deserializeArrayFields<T>(item)).filter((item): item is T => item !== null);
521
-
434
+ const deserializedList = camelResult.map((item) => DbUtils.deserializeArrayFields(item)).filter((item) => item !== null);
522
435
  // 转换 BIGINT 字段(id, pid 等)为数字类型
523
- const lists = convertBigIntFields<T>(deserializedList);
524
-
436
+ const lists = convertBigIntFields(deserializedList);
525
437
  return {
526
438
  data: {
527
439
  lists: lists,
@@ -533,34 +445,27 @@ export class DbHelper {
533
445
  }
534
446
  };
535
447
  }
536
-
537
448
  /**
538
449
  * 插入数据(自动生成 ID、时间戳、state)
539
450
  * @param options.table - 表名(支持小驼峰或下划线格式,会自动转换)
540
451
  */
541
- async insData(options: InsertOptions): Promise<DbResult<number>> {
452
+ async insData(options) {
542
453
  const { table, data } = options;
543
-
544
454
  const snakeTable = snakeCase(table);
545
-
546
455
  const now = Date.now();
547
-
548
- let id: number;
456
+ let id;
549
457
  try {
550
458
  id = await this.redis.genTimeID();
551
- } catch (error: any) {
459
+ }
460
+ catch (error) {
552
461
  throw new Error(`生成 ID 失败,Redis 可能不可用 (table: ${table})`, { cause: error });
553
462
  }
554
-
555
463
  const processed = DbUtils.buildInsertRow({ data: data, id: id, now: now });
556
-
557
464
  // 入口校验:保证进入 SqlBuilder 的数据无 undefined
558
- SqlCheck.assertNoUndefinedInRecord(processed as any, `insData 插入数据 (table: ${snakeTable})`);
559
-
465
+ SqlCheck.assertNoUndefinedInRecord(processed, `insData 插入数据 (table: ${snakeTable})`);
560
466
  // 构建 SQL
561
467
  const builder = this.createSqlBuilder();
562
468
  const { sql, params } = builder.toInsertSql(snakeTable, processed);
563
-
564
469
  // 执行
565
470
  const execRes = await this.executeWithConn(sql, params);
566
471
  const insertedId = processed.id || execRes.data?.lastInsertRowid || 0;
@@ -569,51 +474,43 @@ export class DbHelper {
569
474
  sql: execRes.sql
570
475
  };
571
476
  }
572
-
573
477
  /**
574
478
  * 批量插入数据(真正的批量操作)
575
479
  * 使用 INSERT INTO ... VALUES (...), (...), (...) 语法
576
480
  * 自动生成系统字段并包装在事务中
577
481
  * @param table - 表名(支持小驼峰或下划线格式,会自动转换)
578
482
  */
579
- async insBatch(table: string, dataList: Record<string, any>[]): Promise<DbResult<number[]>> {
483
+ async insBatch(table, dataList) {
580
484
  // 空数组直接返回
581
485
  if (dataList.length === 0) {
582
- const sql: SqlInfo = { sql: "", params: [], duration: 0 };
486
+ const sql = { sql: "", params: [], duration: 0 };
583
487
  return {
584
488
  data: [],
585
489
  sql: sql
586
490
  };
587
491
  }
588
-
589
492
  // 限制批量大小
590
493
  const MAX_BATCH_SIZE = 1000;
591
494
  if (dataList.length > MAX_BATCH_SIZE) {
592
495
  throw new Error(`批量插入数量 ${dataList.length} 超过最大限制 ${MAX_BATCH_SIZE}`);
593
496
  }
594
-
595
497
  // 转换表名:小驼峰 → 下划线
596
498
  const snakeTable = snakeCase(table);
597
-
598
499
  // 批量生成 ID(逐个获取)
599
- const ids: number[] = [];
500
+ const ids = [];
600
501
  for (let i = 0; i < dataList.length; i++) {
601
502
  ids.push(await this.redis.genTimeID());
602
503
  }
603
504
  const now = Date.now();
604
-
605
505
  // 处理所有数据(自动添加系统字段)
606
506
  const processedList = dataList.map((data, index) => {
607
507
  return DbUtils.buildInsertRow({ data: data, id: ids[index], now: now });
608
508
  });
609
-
610
509
  // 入口校验:保证进入 SqlBuilder 的批量数据结构一致且无 undefined
611
- const insertFields = SqlCheck.assertBatchInsertRowsConsistent(processedList as any, { table: snakeTable });
612
-
510
+ const insertFields = SqlCheck.assertBatchInsertRowsConsistent(processedList, { table: snakeTable });
613
511
  // 构建批量插入 SQL
614
512
  const builder = this.createSqlBuilder();
615
513
  const { sql, params } = builder.toInsertSql(snakeTable, processedList);
616
-
617
514
  // 在事务中执行批量插入
618
515
  try {
619
516
  const execRes = await this.executeWithConn(sql, params);
@@ -621,32 +518,27 @@ export class DbHelper {
621
518
  data: ids,
622
519
  sql: execRes.sql
623
520
  };
624
- } catch (error: any) {
625
- Logger.error(
626
- {
627
- err: error,
628
- table: table,
629
- snakeTable: snakeTable,
630
- count: dataList.length,
631
- fields: insertFields
632
- },
633
- "批量插入失败"
634
- );
521
+ }
522
+ catch (error) {
523
+ Logger.error({
524
+ err: error,
525
+ table: table,
526
+ snakeTable: snakeTable,
527
+ count: dataList.length,
528
+ fields: insertFields
529
+ }, "批量插入失败");
635
530
  throw error;
636
531
  }
637
532
  }
638
-
639
- async delForceBatch(table: string, ids: number[]): Promise<DbResult<number>> {
533
+ async delForceBatch(table, ids) {
640
534
  if (ids.length === 0) {
641
- const sql: SqlInfo = { sql: "", params: [], duration: 0 };
535
+ const sql = { sql: "", params: [], duration: 0 };
642
536
  return {
643
537
  data: 0,
644
538
  sql: sql
645
539
  };
646
540
  }
647
-
648
541
  const snakeTable = snakeCase(table);
649
-
650
542
  const query = SqlBuilder.toDeleteInSql({
651
543
  table: snakeTable,
652
544
  idField: "id",
@@ -660,41 +552,33 @@ export class DbHelper {
660
552
  sql: execRes.sql
661
553
  };
662
554
  }
663
-
664
- async updBatch(table: string, dataList: Array<{ id: number; data: Record<string, any> }>): Promise<DbResult<number>> {
555
+ async updBatch(table, dataList) {
665
556
  if (dataList.length === 0) {
666
- const sql: SqlInfo = { sql: "", params: [], duration: 0 };
557
+ const sql = { sql: "", params: [], duration: 0 };
667
558
  return {
668
559
  data: 0,
669
560
  sql: sql
670
561
  };
671
562
  }
672
-
673
563
  const snakeTable = snakeCase(table);
674
564
  const now = Date.now();
675
-
676
- const processedList: Array<{ id: number; data: Record<string, any> }> = [];
677
- const fieldSet = new Set<string>();
678
-
565
+ const processedList = [];
566
+ const fieldSet = new Set();
679
567
  for (const item of dataList) {
680
568
  const userData = DbUtils.buildPartialUpdateData({ data: item.data, allowState: true });
681
-
682
569
  for (const key of Object.keys(userData)) {
683
570
  fieldSet.add(key);
684
571
  }
685
-
686
572
  processedList.push({ id: item.id, data: userData });
687
573
  }
688
-
689
574
  const fields = Array.from(fieldSet).sort();
690
575
  if (fields.length === 0) {
691
- const sql: SqlInfo = { sql: "", params: [], duration: 0 };
576
+ const sql = { sql: "", params: [], duration: 0 };
692
577
  return {
693
578
  data: 0,
694
579
  sql: sql
695
580
  };
696
581
  }
697
-
698
582
  const query = SqlBuilder.toUpdateCaseByIdSql({
699
583
  table: snakeTable,
700
584
  idField: "id",
@@ -706,7 +590,6 @@ export class DbHelper {
706
590
  stateField: "state",
707
591
  stateGtZero: true
708
592
  });
709
-
710
593
  const execRes = await this.executeWithConn(query.sql, query.params);
711
594
  const changes = execRes.data?.changes || 0;
712
595
  return {
@@ -714,28 +597,22 @@ export class DbHelper {
714
597
  sql: execRes.sql
715
598
  };
716
599
  }
717
-
718
600
  /**
719
601
  * 更新数据(强制更新时间戳,系统字段不可修改)
720
602
  * @param options.table - 表名(支持小驼峰或下划线格式,会自动转换)
721
603
  */
722
- async updData(options: UpdateOptions): Promise<DbResult<number>> {
604
+ async updData(options) {
723
605
  const { table, data, where } = options;
724
-
725
606
  // 清理条件(排除 null 和 undefined)
726
607
  const cleanWhere = fieldClear(where, { excludeValues: [null, undefined] });
727
-
728
608
  // 转换表名:小驼峰 → 下划线
729
609
  const snakeTable = snakeCase(table);
730
610
  const snakeWhere = DbUtils.whereKeysToSnake(cleanWhere);
731
-
732
611
  const processed = DbUtils.buildUpdateRow({ data: data, now: Date.now(), allowState: true });
733
-
734
612
  // 构建 SQL
735
613
  const whereFiltered = DbUtils.addDefaultStateFilter(snakeWhere, snakeTable, false);
736
614
  const builder = this.createSqlBuilder().where(whereFiltered);
737
615
  const { sql, params } = builder.toUpdateSql(snakeTable, processed);
738
-
739
616
  // 执行
740
617
  const execRes = await this.executeWithConn(sql, params);
741
618
  const changes = execRes.data?.changes || 0;
@@ -744,39 +621,32 @@ export class DbHelper {
744
621
  sql: execRes.sql
745
622
  };
746
623
  }
747
-
748
624
  /**
749
625
  * 软删除数据(deleted_at 设置为当前时间,state 设置为 0)
750
626
  * @param options.table - 表名(支持小驼峰或下划线格式,会自动转换)
751
627
  */
752
- async delData(options: DeleteOptions): Promise<DbResult<number>> {
628
+ async delData(options) {
753
629
  const { table, where } = options;
754
-
755
630
  return await this.updData({
756
631
  table: table,
757
632
  data: { state: 0, deleted_at: Date.now() },
758
633
  where: where
759
634
  });
760
635
  }
761
-
762
636
  /**
763
637
  * 硬删除数据(物理删除,不可恢复)
764
638
  * @param options.table - 表名(支持小驼峰或下划线格式,会自动转换)
765
639
  */
766
- async delForce(options: Omit<DeleteOptions, "hard">): Promise<DbResult<number>> {
640
+ async delForce(options) {
767
641
  const { table, where } = options;
768
-
769
642
  // 转换表名:小驼峰 → 下划线
770
643
  const snakeTable = snakeCase(table);
771
-
772
644
  // 清理条件字段
773
645
  const cleanWhere = fieldClear(where, { excludeValues: [null, undefined] });
774
646
  const snakeWhere = DbUtils.whereKeysToSnake(cleanWhere);
775
-
776
647
  // 物理删除
777
648
  const builder = this.createSqlBuilder().where(snakeWhere);
778
649
  const { sql, params } = builder.toDeleteSql(snakeTable);
779
-
780
650
  const execRes = await this.executeWithConn(sql, params);
781
651
  const changes = execRes.data?.changes || 0;
782
652
  return {
@@ -784,14 +654,12 @@ export class DbHelper {
784
654
  sql: execRes.sql
785
655
  };
786
656
  }
787
-
788
657
  /**
789
658
  * 禁用数据(设置 state=2)
790
659
  * @param options.table - 表名(支持小驼峰或下划线格式,会自动转换)
791
660
  */
792
- async disableData(options: Omit<DeleteOptions, "hard">): Promise<DbResult<number>> {
661
+ async disableData(options) {
793
662
  const { table, where } = options;
794
-
795
663
  return await this.updData({
796
664
  table: table,
797
665
  data: {
@@ -800,14 +668,12 @@ export class DbHelper {
800
668
  where: where
801
669
  });
802
670
  }
803
-
804
671
  /**
805
672
  * 启用数据(设置 state=1)
806
673
  * @param options.table - 表名(支持小驼峰或下划线格式,会自动转换)
807
674
  */
808
- async enableData(options: Omit<DeleteOptions, "hard">): Promise<DbResult<number>> {
675
+ async enableData(options) {
809
676
  const { table, where } = options;
810
-
811
677
  return await this.updData({
812
678
  table: table,
813
679
  data: {
@@ -816,73 +682,62 @@ export class DbHelper {
816
682
  where: where
817
683
  });
818
684
  }
819
-
820
685
  /**
821
686
  * 执行事务
822
687
  * 使用 Bun SQL 的 begin 方法开启事务
823
688
  */
824
- async trans<T = any>(callback: TransactionCallback<T>): Promise<T> {
689
+ async trans(callback) {
825
690
  if (this.isTransaction) {
826
691
  // 已经在事务中,直接执行回调
827
692
  return await callback(this);
828
693
  }
829
-
830
694
  // 使用 Bun SQL 的 begin 方法开启事务
831
695
  // begin 方法会自动处理 commit/rollback
832
- return await this.sql.begin(async (tx: any) => {
696
+ return await this.sql.begin(async (tx) => {
833
697
  const trans = new DbHelper({ redis: this.redis, sql: tx, dialect: this.dialect });
834
698
  return await callback(trans);
835
699
  });
836
700
  }
837
-
838
701
  /**
839
702
  * 执行原始 SQL
840
703
  */
841
- async query(sql: string, params?: any[]): Promise<DbResult<any>> {
704
+ async query(sql, params) {
842
705
  return await this.executeWithConn(sql, params);
843
706
  }
844
-
845
707
  /**
846
708
  * 检查数据是否存在(优化性能)
847
709
  * @param options.table - 表名(支持小驼峰或下划线格式,会自动转换)
848
710
  */
849
- async exists(options: Omit<QueryOptions, "fields" | "orderBy" | "page" | "limit">): Promise<DbResult<boolean>> {
850
- const { table, where, tableQualifier } = await this.prepareQueryOptions({ ...options, page: 1, limit: 1 } as any);
851
-
711
+ async exists(options) {
712
+ const { table, where, tableQualifier } = await this.prepareQueryOptions({ ...options, page: 1, limit: 1 });
852
713
  // 使用 COUNT(1) 性能更好
853
714
  const builder = this.createSqlBuilder()
854
715
  .selectRaw("COUNT(1) as cnt")
855
716
  .from(table)
856
717
  .where(DbUtils.addDefaultStateFilter(where, tableQualifier, false))
857
718
  .limit(1);
858
-
859
719
  const { sql, params } = builder.toSelectSql();
860
720
  const execRes = await this.executeWithConn(sql, params);
861
721
  const exists = (execRes.data?.[0]?.cnt || 0) > 0;
862
-
863
722
  return {
864
723
  data: exists,
865
724
  sql: execRes.sql
866
725
  };
867
726
  }
868
-
869
727
  /**
870
728
  * 查询单个字段值(带字段名验证)
871
729
  * @param field - 字段名(支持小驼峰或下划线格式)
872
730
  */
873
- async getFieldValue<T = any>(options: Omit<QueryOptions, "fields"> & { field: string }): Promise<DbResult<T | null>> {
731
+ async getFieldValue(options) {
874
732
  const { field, ...queryOptions } = options;
875
-
876
733
  // 验证字段名格式(只允许字母、数字、下划线)
877
734
  if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(field)) {
878
735
  throw new Error(`无效的字段名: ${field},只允许字母、数字和下划线`);
879
736
  }
880
-
881
737
  const oneRes = await this.getOne({
882
738
  ...queryOptions,
883
739
  fields: [field]
884
740
  });
885
-
886
741
  const result = oneRes.data;
887
742
  if (!result) {
888
743
  return {
@@ -890,7 +745,6 @@ export class DbHelper {
890
745
  sql: oneRes.sql
891
746
  };
892
747
  }
893
-
894
748
  // 尝试直接访问字段(小驼峰)
895
749
  if (field in result) {
896
750
  return {
@@ -898,7 +752,6 @@ export class DbHelper {
898
752
  sql: oneRes.sql
899
753
  };
900
754
  }
901
-
902
755
  // 转换为小驼峰格式再尝试访问(支持用户传入下划线格式)
903
756
  const camelField = field.replace(/_([a-z])/g, (_, letter) => letter.toUpperCase());
904
757
  if (camelField !== field && camelField in result) {
@@ -907,7 +760,6 @@ export class DbHelper {
907
760
  sql: oneRes.sql
908
761
  };
909
762
  }
910
-
911
763
  // 转换为下划线格式再尝试访问(支持用户传入小驼峰格式)
912
764
  const snakeField = field.replace(/[A-Z]/g, (letter) => `_${letter.toLowerCase()}`);
913
765
  if (snakeField !== field && snakeField in result) {
@@ -916,54 +768,44 @@ export class DbHelper {
916
768
  sql: oneRes.sql
917
769
  };
918
770
  }
919
-
920
771
  return {
921
772
  data: null,
922
773
  sql: oneRes.sql
923
774
  };
924
775
  }
925
-
926
776
  /**
927
777
  * 自增字段(安全实现,防止 SQL 注入)
928
778
  * @param table - 表名(支持小驼峰或下划线格式,会自动转换)
929
779
  * @param field - 字段名(支持小驼峰或下划线格式,会自动转换)
930
780
  */
931
- async increment(table: string, field: string, where: WhereConditions, value: number = 1): Promise<DbResult<number>> {
781
+ async increment(table, field, where, value = 1) {
932
782
  // 转换表名和字段名:小驼峰 → 下划线
933
783
  const snakeTable = snakeCase(table);
934
784
  const snakeField = snakeCase(field);
935
-
936
785
  // 验证表名格式(只允许字母、数字、下划线)
937
786
  if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(snakeTable)) {
938
787
  throw new Error(`无效的表名: ${snakeTable}`);
939
788
  }
940
-
941
789
  // 验证字段名格式(只允许字母、数字、下划线)
942
790
  if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(snakeField)) {
943
791
  throw new Error(`无效的字段名: ${field}`);
944
792
  }
945
-
946
793
  // 验证 value 必须是数字
947
794
  if (typeof value !== "number" || isNaN(value)) {
948
795
  throw new Error(`自增值必须是有效的数字 (table: ${table}, field: ${field}, value: ${value})`);
949
796
  }
950
-
951
797
  // 清理 where 条件(排除 null 和 undefined)
952
798
  const cleanWhere = fieldClear(where, { excludeValues: [null, undefined] });
953
-
954
799
  // 转换 where 条件字段名:小驼峰 → 下划线
955
800
  const snakeWhere = DbUtils.whereKeysToSnake(cleanWhere);
956
-
957
801
  // 使用 SqlBuilder 构建安全的 WHERE 条件
958
802
  const whereFiltered = DbUtils.addDefaultStateFilter(snakeWhere, snakeTable, false);
959
803
  const builder = this.createSqlBuilder().where(whereFiltered);
960
804
  const { sql: whereClause, params: whereParams } = builder.getWhereConditions();
961
-
962
805
  // 构建安全的 UPDATE SQL(表名和字段名使用反引号转义,已经是下划线格式)
963
806
  const quotedTable = this.dialect.quoteIdent(snakeTable);
964
807
  const quotedField = this.dialect.quoteIdent(snakeField);
965
808
  const sql = whereClause ? `UPDATE ${quotedTable} SET ${quotedField} = ${quotedField} + ? WHERE ${whereClause}` : `UPDATE ${quotedTable} SET ${quotedField} = ${quotedField} + ?`;
966
-
967
809
  const execRes = await this.executeWithConn(sql, [value, ...whereParams]);
968
810
  const changes = execRes.data?.changes || 0;
969
811
  return {
@@ -971,13 +813,12 @@ export class DbHelper {
971
813
  sql: execRes.sql
972
814
  };
973
815
  }
974
-
975
816
  /**
976
817
  * 自减字段
977
818
  * @param table - 表名(支持小驼峰或下划线格式,会自动转换)
978
819
  * @param field - 字段名(支持小驼峰或下划线格式,会自动转换)
979
820
  */
980
- async decrement(table: string, field: string, where: WhereConditions, value: number = 1): Promise<DbResult<number>> {
821
+ async decrement(table, field, where, value = 1) {
981
822
  return await this.increment(table, field, where, -value);
982
823
  }
983
824
  }