baja-lite 1.8.0 → 1.8.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +920 -1
- package/error.d.ts +27 -0
- package/error.js +42 -0
- package/package.json +1 -1
- package/sql.d.ts +5 -0
- package/sql.js +119 -77
- package/string.d.ts +2 -0
- package/string.js +19 -0
package/README.md
CHANGED
|
@@ -1,2 +1,921 @@
|
|
|
1
|
+
# Baja-Lite
|
|
1
2
|
|
|
2
|
-
[](https://www.npmjs.com/package/baja-lite)
|
|
4
|
+
[](https://opensource.org/licenses/MIT)
|
|
5
|
+
|
|
6
|
+
一个功能强大的 TypeScript SQL 抽象层,支持多数据库、ORM、查询构建器、缓存和分布式锁等企业级特性。
|
|
7
|
+
|
|
8
|
+
## 📋 目录
|
|
9
|
+
|
|
10
|
+
- [特性](#特性)
|
|
11
|
+
- [架构](#架构)
|
|
12
|
+
- [安装](#安装)
|
|
13
|
+
- [快速开始](#快速开始)
|
|
14
|
+
- [核心概念](#核心概念)
|
|
15
|
+
- [API 文档](#api-文档)
|
|
16
|
+
- [高级功能](#高级功能)
|
|
17
|
+
|
|
18
|
+
## ✨ 特性
|
|
19
|
+
|
|
20
|
+
- 🗄️ **多数据库支持**: MySQL, PostgreSQL, SQLite, SQLite Remote
|
|
21
|
+
- 🔄 **同步/异步模式**: 根据数据库类型自动选择
|
|
22
|
+
- 🎯 **类型安全**: 完整的 TypeScript 类型支持
|
|
23
|
+
- 🔧 **ORM 功能**: 装饰器驱动的实体定义
|
|
24
|
+
- 🔍 **流式查询**: 链式 API 构建复杂查询
|
|
25
|
+
- 📝 **SQL 模板**: 支持 Mustache、XML (MyBatis 风格)、MU 格式
|
|
26
|
+
- 💾 **缓存系统**: 基于 Redis 的方法级缓存
|
|
27
|
+
- 🔒 **分布式锁**: Redis 实现的方法级锁
|
|
28
|
+
- 📊 **分页查询**: 内置分页支持
|
|
29
|
+
- 🔄 **事务管理**: 支持嵌套事务
|
|
30
|
+
- 📤 **导入导出**: Excel 数据交换支持
|
|
31
|
+
- 🔄 **自动迁移**: SQLite 版本管理和自动升级
|
|
32
|
+
|
|
33
|
+
## 🏗️ 架构
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
37
|
+
│ Application Layer │
|
|
38
|
+
│ (Your Business Logic) │
|
|
39
|
+
└─────────────────────────────────────────────────────────────┘
|
|
40
|
+
│
|
|
41
|
+
▼
|
|
42
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
43
|
+
│ SqlService Layer │
|
|
44
|
+
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
|
45
|
+
│ │ CRUD API │ │ Stream Query │ │ Template │ │
|
|
46
|
+
│ │ insert/update│ │ Fluent API │ │ System │ │
|
|
47
|
+
│ │ delete/select│ │ │ │ │ │
|
|
48
|
+
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
|
49
|
+
└─────────────────────────────────────────────────────────────┘
|
|
50
|
+
│
|
|
51
|
+
▼
|
|
52
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
53
|
+
│ Connection Layer (Dao) │
|
|
54
|
+
│ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │
|
|
55
|
+
│ │ MySQL │ │PostgreSQL│ │ SQLite │ │ Remote │ │
|
|
56
|
+
│ │Connection│ │Connection│ │Connection│ │Connection│ │
|
|
57
|
+
│ └──────────┘ └──────────┘ └──────────┘ └──────────┘ │
|
|
58
|
+
└─────────────────────────────────────────────────────────────┘
|
|
59
|
+
│
|
|
60
|
+
▼
|
|
61
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
62
|
+
│ Database Layer │
|
|
63
|
+
│ MySQL Server │ PostgreSQL │ SQLite File/Memory │
|
|
64
|
+
└─────────────────────────────────────────────────────────────┘
|
|
65
|
+
|
|
66
|
+
┌─────────────────────────────────────────────────────────────┐
|
|
67
|
+
│ Auxiliary Services │
|
|
68
|
+
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
|
|
69
|
+
│ │ Redis Cache │ │ Redis Lock │ │ Logger │ │
|
|
70
|
+
│ └──────────────┘ └──────────────┘ └──────────────┘ │
|
|
71
|
+
└─────────────────────────────────────────────────────────────┘
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### 核心组件
|
|
75
|
+
|
|
76
|
+
1. **Dao (Data Access Object)**
|
|
77
|
+
- 管理数据库连接池
|
|
78
|
+
- 提供事务支持
|
|
79
|
+
- 统一的 CRUD 接口
|
|
80
|
+
|
|
81
|
+
2. **Connection**
|
|
82
|
+
- 封装数据库连接
|
|
83
|
+
- 执行 SQL 语句
|
|
84
|
+
- 管理事务状态
|
|
85
|
+
|
|
86
|
+
3. **SqlService**
|
|
87
|
+
- 业务层服务基类
|
|
88
|
+
- 提供高级 CRUD 方法
|
|
89
|
+
- 集成缓存和锁
|
|
90
|
+
|
|
91
|
+
4. **StreamQuery**
|
|
92
|
+
- 流式查询构建器
|
|
93
|
+
- 链式 API
|
|
94
|
+
- 类型安全的查询条件
|
|
95
|
+
|
|
96
|
+
## 📦 安装
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
npm install baja-lite baja-lite-field
|
|
100
|
+
|
|
101
|
+
# 根据需要安装数据库驱动
|
|
102
|
+
npm install mysql2 # MySQL
|
|
103
|
+
npm install pg pg-pool # PostgreSQL
|
|
104
|
+
npm install better-sqlite3 # SQLite
|
|
105
|
+
|
|
106
|
+
# 可选:缓存和锁
|
|
107
|
+
npm install ioredis redlock
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
## 🚀 快速开始
|
|
111
|
+
|
|
112
|
+
### 1. 初始化数据库
|
|
113
|
+
|
|
114
|
+
```typescript
|
|
115
|
+
import { boot } from 'baja-lite/boot.js';
|
|
116
|
+
import { DBType } from 'baja-lite';
|
|
117
|
+
import Database from 'better-sqlite3';
|
|
118
|
+
|
|
119
|
+
await boot({
|
|
120
|
+
// MySQL 配置
|
|
121
|
+
Mysql: {
|
|
122
|
+
host: 'localhost',
|
|
123
|
+
user: 'root',
|
|
124
|
+
password: 'password',
|
|
125
|
+
database: 'mydb',
|
|
126
|
+
port: 3306
|
|
127
|
+
},
|
|
128
|
+
|
|
129
|
+
// SQLite 配置
|
|
130
|
+
Sqlite: './data.db',
|
|
131
|
+
BetterSqlite3: Database,
|
|
132
|
+
|
|
133
|
+
// Redis 配置(可选)
|
|
134
|
+
Redis: {
|
|
135
|
+
host: 'localhost',
|
|
136
|
+
port: 6379
|
|
137
|
+
},
|
|
138
|
+
|
|
139
|
+
// SQL 模板目录
|
|
140
|
+
sqlDir: './sql',
|
|
141
|
+
|
|
142
|
+
// 日志级别
|
|
143
|
+
log: ['debug', 'info', 'warn', 'error']
|
|
144
|
+
});
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### 2. 定义实体
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
import { Field } from 'baja-lite-field';
|
|
151
|
+
|
|
152
|
+
export class User {
|
|
153
|
+
@Field({
|
|
154
|
+
type: 'String',
|
|
155
|
+
P: 'id',
|
|
156
|
+
id: true,
|
|
157
|
+
uuid: true,
|
|
158
|
+
comment: '用户ID'
|
|
159
|
+
})
|
|
160
|
+
id: string;
|
|
161
|
+
|
|
162
|
+
@Field({
|
|
163
|
+
type: 'String',
|
|
164
|
+
P: 'username',
|
|
165
|
+
comment: '用户名'
|
|
166
|
+
})
|
|
167
|
+
username: string;
|
|
168
|
+
|
|
169
|
+
@Field({
|
|
170
|
+
type: 'String',
|
|
171
|
+
P: 'email',
|
|
172
|
+
comment: '邮箱'
|
|
173
|
+
})
|
|
174
|
+
email: string;
|
|
175
|
+
|
|
176
|
+
@Field({
|
|
177
|
+
type: 'Number',
|
|
178
|
+
P: 'age',
|
|
179
|
+
comment: '年龄'
|
|
180
|
+
})
|
|
181
|
+
age: number;
|
|
182
|
+
|
|
183
|
+
@Field({
|
|
184
|
+
type: 'Date',
|
|
185
|
+
P: 'created_at',
|
|
186
|
+
comment: '创建时间'
|
|
187
|
+
})
|
|
188
|
+
createdAt: Date;
|
|
189
|
+
}
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### 3. 创建服务
|
|
193
|
+
|
|
194
|
+
```typescript
|
|
195
|
+
import { DB, SqlService, SyncMode, InsertMode } from 'baja-lite';
|
|
196
|
+
|
|
197
|
+
@DB({
|
|
198
|
+
tableName: 'user',
|
|
199
|
+
clz: User,
|
|
200
|
+
dbType: DBType.Mysql,
|
|
201
|
+
comment: '用户表'
|
|
202
|
+
})
|
|
203
|
+
export class UserService extends SqlService<User> {
|
|
204
|
+
|
|
205
|
+
// 插入用户
|
|
206
|
+
async createUser(user: User) {
|
|
207
|
+
return await this.insert({
|
|
208
|
+
data: user,
|
|
209
|
+
mode: InsertMode.Insert
|
|
210
|
+
});
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// 更新用户
|
|
214
|
+
async updateUser(user: Partial<User>) {
|
|
215
|
+
return await this.update({
|
|
216
|
+
data: user
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// 删除用户
|
|
221
|
+
async deleteUser(id: string) {
|
|
222
|
+
return await this.delete({
|
|
223
|
+
id
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// 查询用户
|
|
228
|
+
async getUser(id: string) {
|
|
229
|
+
return await this.template({
|
|
230
|
+
templateResult: TemplateResult.NotSureOne,
|
|
231
|
+
id
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// 分页查询
|
|
236
|
+
async listUsers(page: number, size: number) {
|
|
237
|
+
return await this.stream()
|
|
238
|
+
.select('id', 'username', 'email', 'age')
|
|
239
|
+
.asc('createdAt')
|
|
240
|
+
.page(page, size)
|
|
241
|
+
.excutePage();
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
### 4. 使用服务
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
const userService = new UserService();
|
|
250
|
+
|
|
251
|
+
// 创建用户
|
|
252
|
+
const userId = await userService.createUser({
|
|
253
|
+
username: 'john',
|
|
254
|
+
email: 'john@example.com',
|
|
255
|
+
age: 25
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
// 查询用户
|
|
259
|
+
const user = await userService.getUser(userId);
|
|
260
|
+
|
|
261
|
+
// 更新用户
|
|
262
|
+
await userService.updateUser({
|
|
263
|
+
id: userId,
|
|
264
|
+
age: 26
|
|
265
|
+
});
|
|
266
|
+
|
|
267
|
+
// 分页查询
|
|
268
|
+
const result = await userService.listUsers(1, 10);
|
|
269
|
+
console.log(result.records); // 用户列表
|
|
270
|
+
console.log(result.total); // 总数
|
|
271
|
+
```
|
|
272
|
+
|
|
273
|
+
## 🔑 核心概念
|
|
274
|
+
|
|
275
|
+
### 1. 同步模式 (SyncMode)
|
|
276
|
+
|
|
277
|
+
```typescript
|
|
278
|
+
// 异步模式(MySQL, PostgreSQL, SQLite Remote)
|
|
279
|
+
const users = await service.select({
|
|
280
|
+
sync: SyncMode.Async,
|
|
281
|
+
sql: 'SELECT * FROM user'
|
|
282
|
+
});
|
|
283
|
+
|
|
284
|
+
// 同步模式(SQLite)
|
|
285
|
+
const users = service.select({
|
|
286
|
+
sync: SyncMode.Sync,
|
|
287
|
+
sql: 'SELECT * FROM user'
|
|
288
|
+
});
|
|
289
|
+
```
|
|
290
|
+
|
|
291
|
+
### 2. 插入模式 (InsertMode)
|
|
292
|
+
|
|
293
|
+
```typescript
|
|
294
|
+
// 普通插入
|
|
295
|
+
await service.insert({
|
|
296
|
+
data: user,
|
|
297
|
+
mode: InsertMode.Insert
|
|
298
|
+
});
|
|
299
|
+
|
|
300
|
+
// 不存在则插入
|
|
301
|
+
await service.insert({
|
|
302
|
+
data: user,
|
|
303
|
+
mode: InsertMode.InsertIfNotExists,
|
|
304
|
+
existConditionOtherThanIds: ['email']
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
// 替换(存在则更新)
|
|
308
|
+
await service.insert({
|
|
309
|
+
data: user,
|
|
310
|
+
mode: InsertMode.Replace
|
|
311
|
+
});
|
|
312
|
+
|
|
313
|
+
// 使用临时表批量插入
|
|
314
|
+
await service.insert({
|
|
315
|
+
data: users,
|
|
316
|
+
mode: InsertMode.InsertWithTempTable
|
|
317
|
+
});
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
### 3. 查询结果类型 (SelectResult)
|
|
321
|
+
|
|
322
|
+
```typescript
|
|
323
|
+
// 一行一列,确定非空
|
|
324
|
+
const count = await service.select({
|
|
325
|
+
selectResult: SelectResult.R_C_Assert,
|
|
326
|
+
sql: 'SELECT COUNT(*) FROM user'
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
// 一行多列,可能为空
|
|
330
|
+
const user = await service.select({
|
|
331
|
+
selectResult: SelectResult.R_CS_NotSure,
|
|
332
|
+
sql: 'SELECT * FROM user WHERE id = ?',
|
|
333
|
+
params: { id: 1 }
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
// 多行多列
|
|
337
|
+
const users = await service.select({
|
|
338
|
+
selectResult: SelectResult.RS_CS,
|
|
339
|
+
sql: 'SELECT * FROM user'
|
|
340
|
+
});
|
|
341
|
+
```
|
|
342
|
+
|
|
343
|
+
## 📚 API 文档
|
|
344
|
+
|
|
345
|
+
### SqlService 核心方法
|
|
346
|
+
|
|
347
|
+
#### insert(option)
|
|
348
|
+
插入数据
|
|
349
|
+
|
|
350
|
+
```typescript
|
|
351
|
+
interface InsertOption {
|
|
352
|
+
data: T | T[]; // 要插入的数据
|
|
353
|
+
sync?: SyncMode; // 同步模式
|
|
354
|
+
mode?: InsertMode; // 插入模式
|
|
355
|
+
existConditionOtherThanIds?: (keyof T)[]; // 判断存在的字段
|
|
356
|
+
skipUndefined?: boolean; // 跳过 undefined
|
|
357
|
+
skipNull?: boolean; // 跳过 null
|
|
358
|
+
skipEmptyString?: boolean; // 跳过空字符串
|
|
359
|
+
maxDeal?: number; // 批量处理数量
|
|
360
|
+
conn?: Connection; // 连接对象
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// 返回插入的 ID
|
|
364
|
+
const id = await service.insert({ data: user });
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
#### update(option)
|
|
368
|
+
更新数据
|
|
369
|
+
|
|
370
|
+
```typescript
|
|
371
|
+
interface UpdateOption {
|
|
372
|
+
data: Partial<T> | Partial<T>[]; // 要更新的数据(必须包含 ID)
|
|
373
|
+
sync?: SyncMode;
|
|
374
|
+
skipUndefined?: boolean;
|
|
375
|
+
skipNull?: boolean;
|
|
376
|
+
skipEmptyString?: boolean;
|
|
377
|
+
maxDeal?: number;
|
|
378
|
+
conn?: Connection;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// 返回影响的行数
|
|
382
|
+
const count = await service.update({ data: user });
|
|
383
|
+
```
|
|
384
|
+
|
|
385
|
+
#### delete(option)
|
|
386
|
+
删除数据
|
|
387
|
+
|
|
388
|
+
```typescript
|
|
389
|
+
interface DeleteOption {
|
|
390
|
+
sync?: SyncMode;
|
|
391
|
+
id?: string | number | Array<string | number>; // 按 ID 删除
|
|
392
|
+
where?: Partial<T> | Array<Partial<T>>; // 按条件删除
|
|
393
|
+
mode?: DeleteMode; // 删除模式
|
|
394
|
+
forceDelete?: boolean; // 强制删除(忽略逻辑删除)
|
|
395
|
+
whereSql?: string; // 自定义 WHERE
|
|
396
|
+
whereParams?: Record<string, any>;
|
|
397
|
+
conn?: Connection;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// 返回影响的行数
|
|
401
|
+
const count = await service.delete({ id: '123' });
|
|
402
|
+
```
|
|
403
|
+
|
|
404
|
+
#### select(option)
|
|
405
|
+
自由查询
|
|
406
|
+
|
|
407
|
+
```typescript
|
|
408
|
+
interface SelectOption {
|
|
409
|
+
sync?: SyncMode;
|
|
410
|
+
selectResult?: SelectResult; // 结果类型
|
|
411
|
+
sqlId?: string; // SQL 模板 ID
|
|
412
|
+
sql?: string; // SQL 语句
|
|
413
|
+
params?: Record<string, any>; // 参数
|
|
414
|
+
context?: any; // 上下文
|
|
415
|
+
hump?: boolean; // 驼峰转换
|
|
416
|
+
mapper?: string | SqlMapper; // 结果映射
|
|
417
|
+
dataConvert?: Record<string, string>; // 数据转换
|
|
418
|
+
conn?: Connection;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
const users = await service.select({
|
|
422
|
+
sql: 'SELECT * FROM user WHERE age > :age',
|
|
423
|
+
params: { age: 18 }
|
|
424
|
+
});
|
|
425
|
+
```
|
|
426
|
+
|
|
427
|
+
#### page(option)
|
|
428
|
+
分页查询
|
|
429
|
+
|
|
430
|
+
```typescript
|
|
431
|
+
interface PageOption {
|
|
432
|
+
sync?: SyncMode;
|
|
433
|
+
sqlId: string; // SQL 模板 ID
|
|
434
|
+
params: Record<string, any>; // 参数
|
|
435
|
+
pageSize?: number; // 每页数量
|
|
436
|
+
pageNumber?: number; // 页码
|
|
437
|
+
sortName?: string; // 排序字段
|
|
438
|
+
sortType?: string; // 排序方式 ASC/DESC
|
|
439
|
+
sum?: boolean; // 是否汇总
|
|
440
|
+
hump?: boolean;
|
|
441
|
+
mapper?: string | SqlMapper;
|
|
442
|
+
conn?: Connection;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
const result = await service.page({
|
|
446
|
+
sqlId: 'user.list',
|
|
447
|
+
params: { status: 1 },
|
|
448
|
+
pageNumber: 1,
|
|
449
|
+
pageSize: 10
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
// result: { records: T[], total: number, size: number, sum?: {} }
|
|
453
|
+
```
|
|
454
|
+
|
|
455
|
+
#### transaction(option)
|
|
456
|
+
事务管理
|
|
457
|
+
|
|
458
|
+
```typescript
|
|
459
|
+
const result = await service.transaction({
|
|
460
|
+
sync: SyncMode.Async,
|
|
461
|
+
fn: async (conn) => {
|
|
462
|
+
await service.insert({ data: user1, conn });
|
|
463
|
+
await service.insert({ data: user2, conn });
|
|
464
|
+
return 'success';
|
|
465
|
+
}
|
|
466
|
+
});
|
|
467
|
+
```
|
|
468
|
+
|
|
469
|
+
### StreamQuery 流式查询
|
|
470
|
+
|
|
471
|
+
#### 条件方法
|
|
472
|
+
|
|
473
|
+
```typescript
|
|
474
|
+
const query = service.stream();
|
|
475
|
+
|
|
476
|
+
// 相等
|
|
477
|
+
query.eq('username', 'john');
|
|
478
|
+
query.eqs(['username', 'email'], 'john'); // OR 条件
|
|
479
|
+
|
|
480
|
+
// 不等
|
|
481
|
+
query.notEq('status', 0);
|
|
482
|
+
|
|
483
|
+
// 比较
|
|
484
|
+
query.grate('age', 18); // >
|
|
485
|
+
query.grateEq('age', 18); // >=
|
|
486
|
+
query.less('age', 60); // <
|
|
487
|
+
query.lessEq('age', 60); // <=
|
|
488
|
+
|
|
489
|
+
// 模糊查询
|
|
490
|
+
query.like('username', 'john'); // %john%
|
|
491
|
+
query.leftLike('username', 'john'); // %john
|
|
492
|
+
query.rightLike('username', 'john'); // john%
|
|
493
|
+
|
|
494
|
+
// 范围
|
|
495
|
+
query.in('status', [1, 2, 3]);
|
|
496
|
+
query.between('age', 18, 60);
|
|
497
|
+
|
|
498
|
+
// 空值
|
|
499
|
+
query.isNULL('deletedAt');
|
|
500
|
+
query.isNotNULL('deletedAt');
|
|
501
|
+
query.isEmpty('remark');
|
|
502
|
+
query.isNotEmpty('remark');
|
|
503
|
+
|
|
504
|
+
// 正则
|
|
505
|
+
query.regexp('email', '^[a-z]+@');
|
|
506
|
+
|
|
507
|
+
// 全文搜索
|
|
508
|
+
query.match('keyword', ['title', 'content']);
|
|
509
|
+
|
|
510
|
+
// 位运算
|
|
511
|
+
query.pow('permission', 4); // POW(2, permission) & 4
|
|
512
|
+
|
|
513
|
+
// 组合条件
|
|
514
|
+
query.and(q => {
|
|
515
|
+
q.eq('status', 1);
|
|
516
|
+
q.grate('age', 18);
|
|
517
|
+
});
|
|
518
|
+
|
|
519
|
+
query.or(q => {
|
|
520
|
+
q.eq('type', 'A');
|
|
521
|
+
q.eq('type', 'B');
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
// 自定义 WHERE
|
|
525
|
+
query.where('t.score > :score', { score: 90 });
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
#### 聚合方法
|
|
529
|
+
|
|
530
|
+
```typescript
|
|
531
|
+
query.count('ct'); // COUNT(1)
|
|
532
|
+
query.countDistinct('userId', 'uct'); // COUNT(DISTINCT userId)
|
|
533
|
+
query.sum('amount', 'total'); // SUM(amount)
|
|
534
|
+
query.avg('score', 'avgScore'); // AVG(score)
|
|
535
|
+
query.max('price', 'maxPrice'); // MAX(price)
|
|
536
|
+
query.min('price', 'minPrice'); // MIN(price)
|
|
537
|
+
query.groupConcat('tags', { // GROUP_CONCAT
|
|
538
|
+
distinct: true,
|
|
539
|
+
separator: ',',
|
|
540
|
+
asc: ['order']
|
|
541
|
+
});
|
|
542
|
+
```
|
|
543
|
+
|
|
544
|
+
#### 分组和排序
|
|
545
|
+
|
|
546
|
+
```typescript
|
|
547
|
+
query.groupBy('category', 'status');
|
|
548
|
+
query.asc('createdAt', 'id');
|
|
549
|
+
query.desc('score');
|
|
550
|
+
```
|
|
551
|
+
|
|
552
|
+
#### 分页
|
|
553
|
+
|
|
554
|
+
```typescript
|
|
555
|
+
query.limit(0, 10); // LIMIT 0, 10
|
|
556
|
+
query.page(1, 10); // 第 1 页,每页 10 条
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
#### 执行查询
|
|
560
|
+
|
|
561
|
+
```typescript
|
|
562
|
+
// 查询列表
|
|
563
|
+
const users = await query.excuteSelect();
|
|
564
|
+
|
|
565
|
+
// 查询单条
|
|
566
|
+
const user = await query.excuteSelect({
|
|
567
|
+
selectResult: SelectResult.R_CS_NotSure
|
|
568
|
+
});
|
|
569
|
+
|
|
570
|
+
// 分页查询
|
|
571
|
+
const result = await query.excutePage();
|
|
572
|
+
|
|
573
|
+
// 更新
|
|
574
|
+
query.update('status', 1);
|
|
575
|
+
query.incr('viewCount', 1);
|
|
576
|
+
const count = await query.excuteUpdate();
|
|
577
|
+
|
|
578
|
+
// 删除
|
|
579
|
+
const count = await query.excuteDelete();
|
|
580
|
+
```
|
|
581
|
+
|
|
582
|
+
## 🚀 高级功能
|
|
583
|
+
|
|
584
|
+
### 1. SQL 模板系统
|
|
585
|
+
|
|
586
|
+
#### Mustache 模板
|
|
587
|
+
|
|
588
|
+
创建 `sql/user.ts`:
|
|
589
|
+
|
|
590
|
+
```typescript
|
|
591
|
+
export default {
|
|
592
|
+
list: (options) => `
|
|
593
|
+
SELECT
|
|
594
|
+
{{#page}} COUNT(1) ct {{/page}}
|
|
595
|
+
{{#notPage}} * {{/notPage}}
|
|
596
|
+
FROM user t
|
|
597
|
+
{{#where}}
|
|
598
|
+
{{#params.status}}
|
|
599
|
+
AND t.status = :status
|
|
600
|
+
{{/params.status}}
|
|
601
|
+
{{#params.keyword}}
|
|
602
|
+
AND t.username LIKE CONCAT('%', :keyword, '%')
|
|
603
|
+
{{/params.keyword}}
|
|
604
|
+
{{/where}}
|
|
605
|
+
{{#order}} t.created_at DESC {{/order}}
|
|
606
|
+
`
|
|
607
|
+
};
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
使用:
|
|
611
|
+
|
|
612
|
+
```typescript
|
|
613
|
+
const result = await service.page({
|
|
614
|
+
sqlId: 'user.list',
|
|
615
|
+
params: { status: 1, keyword: 'john' },
|
|
616
|
+
pageNumber: 1,
|
|
617
|
+
pageSize: 10
|
|
618
|
+
});
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
#### XML 模板 (MyBatis 风格)
|
|
622
|
+
|
|
623
|
+
创建 `sql/user.xml`:
|
|
624
|
+
|
|
625
|
+
```xml
|
|
626
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
|
627
|
+
<mapper>
|
|
628
|
+
<resultMap id="userMap">
|
|
629
|
+
<id column="id" property="id"/>
|
|
630
|
+
<result column="username" property="username"/>
|
|
631
|
+
<result column="email" property="email"/>
|
|
632
|
+
</resultMap>
|
|
633
|
+
|
|
634
|
+
<select id="list" resultMap="userMap">
|
|
635
|
+
SELECT * FROM user
|
|
636
|
+
WHERE status = :status
|
|
637
|
+
<if test="keyword != null">
|
|
638
|
+
AND username LIKE CONCAT('%', :keyword, '%')
|
|
639
|
+
</if>
|
|
640
|
+
ORDER BY created_at DESC
|
|
641
|
+
</select>
|
|
642
|
+
</mapper>
|
|
643
|
+
```
|
|
644
|
+
|
|
645
|
+
### 2. 缓存系统
|
|
646
|
+
|
|
647
|
+
```typescript
|
|
648
|
+
import { MethodCache, clearMethodCache } from 'baja-lite';
|
|
649
|
+
|
|
650
|
+
class UserService extends SqlService<User> {
|
|
651
|
+
|
|
652
|
+
@MethodCache({
|
|
653
|
+
key: (id) => `user:${id}`,
|
|
654
|
+
autoClearTime: 60 // 60 分钟后自动清除
|
|
655
|
+
})
|
|
656
|
+
async getUser(id: string) {
|
|
657
|
+
return await this.template({
|
|
658
|
+
templateResult: TemplateResult.NotSureOne,
|
|
659
|
+
id
|
|
660
|
+
});
|
|
661
|
+
}
|
|
662
|
+
|
|
663
|
+
@MethodCache({
|
|
664
|
+
key: (status) => `user:list:${status}`,
|
|
665
|
+
clearKey: (status) => ['user:list'] // 关联清除
|
|
666
|
+
})
|
|
667
|
+
async listByStatus(status: number) {
|
|
668
|
+
return await this.stream()
|
|
669
|
+
.eq('status', status)
|
|
670
|
+
.excuteSelect();
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
// 手动清除缓存
|
|
675
|
+
await clearMethodCache('user:123');
|
|
676
|
+
await clearMethodCache('user:list'); // 清除所有关联缓存
|
|
677
|
+
```
|
|
678
|
+
|
|
679
|
+
### 3. 分布式锁
|
|
680
|
+
|
|
681
|
+
```typescript
|
|
682
|
+
import { MethodLock, excuteWithLock } from 'baja-lite';
|
|
683
|
+
|
|
684
|
+
class OrderService extends SqlService<Order> {
|
|
685
|
+
|
|
686
|
+
@MethodLock({
|
|
687
|
+
key: (orderId) => `order:${orderId}`,
|
|
688
|
+
lockMaxActive: 1, // 最大并发数
|
|
689
|
+
lockMaxTime: 30000, // 锁超时时间(毫秒)
|
|
690
|
+
lockWait: true, // 等待锁释放
|
|
691
|
+
lockRetryInterval: 100 // 重试间隔
|
|
692
|
+
})
|
|
693
|
+
async processOrder(orderId: string) {
|
|
694
|
+
// 业务逻辑
|
|
695
|
+
const order = await this.getOrder(orderId);
|
|
696
|
+
// ...
|
|
697
|
+
return order;
|
|
698
|
+
}
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
// 手动使用锁
|
|
702
|
+
await excuteWithLock({
|
|
703
|
+
key: 'critical-section',
|
|
704
|
+
lockMaxActive: 1
|
|
705
|
+
}, async () => {
|
|
706
|
+
// 临界区代码
|
|
707
|
+
});
|
|
708
|
+
```
|
|
709
|
+
|
|
710
|
+
### 4. 数据映射
|
|
711
|
+
|
|
712
|
+
```typescript
|
|
713
|
+
// 定义映射
|
|
714
|
+
const userMapper: SqlMapper = [
|
|
715
|
+
{ columnName: 'user_id', mapNames: ['id'] },
|
|
716
|
+
{ columnName: 'user_name', mapNames: ['name'] },
|
|
717
|
+
{ columnName: 'dept_id', mapNames: ['department', 'id'] },
|
|
718
|
+
{ columnName: 'dept_name', mapNames: ['department', 'name'] }
|
|
719
|
+
];
|
|
720
|
+
|
|
721
|
+
// 使用映射
|
|
722
|
+
const users = await service.select({
|
|
723
|
+
sql: 'SELECT user_id, user_name, dept_id, dept_name FROM user',
|
|
724
|
+
mapper: userMapper
|
|
725
|
+
});
|
|
726
|
+
|
|
727
|
+
// 结果:
|
|
728
|
+
// [
|
|
729
|
+
// {
|
|
730
|
+
// id: 1,
|
|
731
|
+
// name: 'John',
|
|
732
|
+
// department: { id: 10, name: 'IT' }
|
|
733
|
+
// }
|
|
734
|
+
// ]
|
|
735
|
+
```
|
|
736
|
+
|
|
737
|
+
### 5. 数据转换
|
|
738
|
+
|
|
739
|
+
```typescript
|
|
740
|
+
// 初始化时配置
|
|
741
|
+
await boot({
|
|
742
|
+
dataConvert: {
|
|
743
|
+
qiniu: (filename) => `https://cdn.example.com/${filename}`,
|
|
744
|
+
date: (timestamp) => new Date(timestamp)
|
|
745
|
+
}
|
|
746
|
+
});
|
|
747
|
+
|
|
748
|
+
// 使用转换
|
|
749
|
+
const users = await service.select({
|
|
750
|
+
sql: 'SELECT id, avatar, created_at FROM user',
|
|
751
|
+
dataConvert: {
|
|
752
|
+
avatar: 'qiniu',
|
|
753
|
+
created_at: 'date'
|
|
754
|
+
}
|
|
755
|
+
});
|
|
756
|
+
```
|
|
757
|
+
|
|
758
|
+
### 6. 导入导出
|
|
759
|
+
|
|
760
|
+
```typescript
|
|
761
|
+
// 导出数据
|
|
762
|
+
const users = await service.stream().excuteSelect();
|
|
763
|
+
const exportData = service.exp(users);
|
|
764
|
+
|
|
765
|
+
// exportData 可直接用于 EJS-Excel
|
|
766
|
+
// {
|
|
767
|
+
// title: '用户表',
|
|
768
|
+
// titleSpan: 'A1:E1',
|
|
769
|
+
// columnTitles: ['ID', '用户名', '邮箱', '年龄', '创建时间'],
|
|
770
|
+
// datas: [...]
|
|
771
|
+
// }
|
|
772
|
+
|
|
773
|
+
// 导入模板
|
|
774
|
+
const template = service.imp();
|
|
775
|
+
```
|
|
776
|
+
|
|
777
|
+
### 7. 事务嵌套
|
|
778
|
+
|
|
779
|
+
```typescript
|
|
780
|
+
await service.transaction({
|
|
781
|
+
fn: async (conn) => {
|
|
782
|
+
// 外层事务
|
|
783
|
+
await service.insert({ data: user1, conn });
|
|
784
|
+
|
|
785
|
+
// 嵌套事务
|
|
786
|
+
await service.transaction({
|
|
787
|
+
conn, // 传递连接
|
|
788
|
+
fn: async (conn2) => {
|
|
789
|
+
await service.insert({ data: user2, conn: conn2 });
|
|
790
|
+
await service.update({ data: user3, conn: conn2 });
|
|
791
|
+
}
|
|
792
|
+
});
|
|
793
|
+
|
|
794
|
+
await service.insert({ data: user4, conn });
|
|
795
|
+
}
|
|
796
|
+
});
|
|
797
|
+
```
|
|
798
|
+
|
|
799
|
+
## 🔧 配置选项
|
|
800
|
+
|
|
801
|
+
### GlobalSqlOption
|
|
802
|
+
|
|
803
|
+
```typescript
|
|
804
|
+
interface GlobalSqlOption {
|
|
805
|
+
// 数据库配置
|
|
806
|
+
Mysql?: Record<string, any>; // MySQL 配置
|
|
807
|
+
Postgresql?: Record<string, any>; // PostgreSQL 配置
|
|
808
|
+
Sqlite?: string | Record<string, string>; // SQLite 配置
|
|
809
|
+
BetterSqlite3?: any; // SQLite 驱动
|
|
810
|
+
|
|
811
|
+
// Redis 配置
|
|
812
|
+
Redis?: Record<string, any>;
|
|
813
|
+
|
|
814
|
+
// SQL 模板
|
|
815
|
+
sqlDir?: string; // SQL 目录
|
|
816
|
+
sqlMap?: SqlModel; // SQL 映射
|
|
817
|
+
sqlFNDir?: string; // SQL 函数目录
|
|
818
|
+
sqlMapperDir?: string; // 映射目录
|
|
819
|
+
|
|
820
|
+
// 行为配置
|
|
821
|
+
skipUndefined?: boolean; // 默认 true
|
|
822
|
+
skipNull?: boolean; // 默认 true
|
|
823
|
+
skipEmptyString?: boolean; // 默认 true
|
|
824
|
+
maxDeal?: number; // 默认 500
|
|
825
|
+
|
|
826
|
+
// 列名转换
|
|
827
|
+
columnMode?: ColumnMode; // NONE | HUMP
|
|
828
|
+
|
|
829
|
+
// 数据转换
|
|
830
|
+
dataConvert?: Record<string, (data: any) => any>;
|
|
831
|
+
|
|
832
|
+
// 日志
|
|
833
|
+
log?: LogLevel[] | LogLevel;
|
|
834
|
+
logger?: LoggerService;
|
|
835
|
+
|
|
836
|
+
// 上下文
|
|
837
|
+
ctx?: any;
|
|
838
|
+
}
|
|
839
|
+
```
|
|
840
|
+
|
|
841
|
+
## 📝 最佳实践
|
|
842
|
+
|
|
843
|
+
### 1. 服务组织
|
|
844
|
+
|
|
845
|
+
```typescript
|
|
846
|
+
// services/base.service.ts
|
|
847
|
+
export abstract class BaseService<T> extends SqlService<T> {
|
|
848
|
+
// 通用方法
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
// services/user.service.ts
|
|
852
|
+
@DB({ tableName: 'user', clz: User })
|
|
853
|
+
export class UserService extends BaseService<User> {
|
|
854
|
+
// 用户特定方法
|
|
855
|
+
}
|
|
856
|
+
```
|
|
857
|
+
|
|
858
|
+
### 2. 错误处理
|
|
859
|
+
|
|
860
|
+
```typescript
|
|
861
|
+
try {
|
|
862
|
+
await service.transaction({
|
|
863
|
+
fn: async (conn) => {
|
|
864
|
+
// 业务逻辑
|
|
865
|
+
}
|
|
866
|
+
});
|
|
867
|
+
} catch (error) {
|
|
868
|
+
console.error('Transaction failed:', error);
|
|
869
|
+
// 错误处理
|
|
870
|
+
}
|
|
871
|
+
```
|
|
872
|
+
|
|
873
|
+
### 3. 性能优化
|
|
874
|
+
|
|
875
|
+
```typescript
|
|
876
|
+
// 使用批量操作
|
|
877
|
+
await service.insert({
|
|
878
|
+
data: users, // 数组
|
|
879
|
+
maxDeal: 1000 // 每次处理 1000 条
|
|
880
|
+
});
|
|
881
|
+
|
|
882
|
+
// 使用临时表(大批量)
|
|
883
|
+
await service.insert({
|
|
884
|
+
data: users,
|
|
885
|
+
mode: InsertMode.InsertWithTempTable
|
|
886
|
+
});
|
|
887
|
+
|
|
888
|
+
// 使用流式查询避免 N+1
|
|
889
|
+
const users = await service.stream()
|
|
890
|
+
.select('id', 'username') // 只查询需要的字段
|
|
891
|
+
.limit(0, 100)
|
|
892
|
+
.excuteSelect();
|
|
893
|
+
```
|
|
894
|
+
|
|
895
|
+
### 4. 类型安全
|
|
896
|
+
|
|
897
|
+
```typescript
|
|
898
|
+
// 使用泛型
|
|
899
|
+
class UserService extends SqlService<User> {
|
|
900
|
+
async findByEmail(email: string): Promise<User | null> {
|
|
901
|
+
return await this.stream()
|
|
902
|
+
.eq('email', email) // 类型检查
|
|
903
|
+
.excuteSelect({
|
|
904
|
+
selectResult: SelectResult.R_CS_NotSure
|
|
905
|
+
});
|
|
906
|
+
}
|
|
907
|
+
}
|
|
908
|
+
```
|
|
909
|
+
|
|
910
|
+
## 📄 License
|
|
911
|
+
|
|
912
|
+
MIT
|
|
913
|
+
|
|
914
|
+
## 🤝 贡献
|
|
915
|
+
|
|
916
|
+
欢迎提交 Issue 和 Pull Request!
|
|
917
|
+
|
|
918
|
+
## 📮 联系
|
|
919
|
+
|
|
920
|
+
- GitHub: [void-soul/baja-lite](https://github.com/void-soul/baja-lite)
|
|
921
|
+
- NPM: [baja-lite](https://www.npmjs.com/package/baja-lite)
|