chanjs 2.7.4 → 2.7.5

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 (93) hide show
  1. package/USAGE.md +533 -0
  2. package/config/index.js +37 -6
  3. package/core/App.js +166 -0
  4. package/core/Container.js +77 -0
  5. package/core/Controller.js +29 -0
  6. package/core/Database.js +93 -0
  7. package/core/Repository.js +327 -0
  8. package/core/Service.js +11 -0
  9. package/core/bootstrap/error-handler.js +104 -0
  10. package/core/bootstrap/hook-runner.js +64 -0
  11. package/core/bootstrap/middleware.js +35 -0
  12. package/core/bootstrap/router-loader.js +53 -0
  13. package/core/errors.js +224 -0
  14. package/core/loader.js +89 -0
  15. package/core/registry.js +17 -0
  16. package/doc/Cache.md +279 -106
  17. package/doc/Common.md +590 -134
  18. package/doc/Controller.md +166 -95
  19. package/doc/Help.md +299 -698
  20. package/doc/QuickStart.md +116 -0
  21. package/doc/Repository.md +560 -0
  22. package/doc/Service.md +201 -527
  23. package/index.js +75 -37
  24. package/middleware/body.js +17 -0
  25. package/middleware/cookie.js +7 -15
  26. package/middleware/cors.js +9 -27
  27. package/middleware/favicon.js +7 -17
  28. package/middleware/header.js +15 -16
  29. package/middleware/index.js +11 -11
  30. package/middleware/log.js +26 -56
  31. package/middleware/static.js +15 -28
  32. package/middleware/template.js +75 -115
  33. package/middleware/validate.js +79 -0
  34. package/middleware/waf.js +174 -197
  35. package/package.json +9 -2
  36. package/response/code.js +73 -0
  37. package/response/index.js +9 -6
  38. package/response/response.js +82 -236
  39. package/security/checker.js +26 -74
  40. package/security/index.js +4 -9
  41. package/security/jwt.js +69 -142
  42. package/security/keywords.js +32 -136
  43. package/security/rate-limit.js +38 -80
  44. package/security/sign.js +83 -176
  45. package/security/xss-filter.js +21 -53
  46. package/storage/cache.js +57 -196
  47. package/storage/index.js +3 -6
  48. package/storage/redis.js +123 -181
  49. package/storage/store.js +163 -188
  50. package/utils/data-parse.js +42 -186
  51. package/utils/file.js +73 -244
  52. package/utils/filter.js +22 -25
  53. package/utils/html.js +49 -33
  54. package/utils/index.js +21 -7
  55. package/utils/ip.js +31 -71
  56. package/utils/logger.js +117 -0
  57. package/utils/pages.js +55 -0
  58. package/utils/paths.js +18 -0
  59. package/utils/request.js +94 -136
  60. package/utils/signal.js +87 -0
  61. package/utils/time.js +33 -75
  62. package/utils/tree.js +112 -104
  63. package/App.js +0 -533
  64. package/base/Aop.js +0 -195
  65. package/base/Container.js +0 -161
  66. package/base/Controller.js +0 -65
  67. package/base/Database.js +0 -133
  68. package/base/Event.js +0 -61
  69. package/base/Repository.js +0 -644
  70. package/common/api.js +0 -35
  71. package/common/code.js +0 -52
  72. package/common/email.js +0 -191
  73. package/common/index.js +0 -5
  74. package/common/pages.js +0 -120
  75. package/common/utils.js +0 -73
  76. package/config/code.js +0 -166
  77. package/config/paths.js +0 -60
  78. package/doc/Aop.md +0 -269
  79. package/doc/Email.md +0 -114
  80. package/doc/Event.md +0 -232
  81. package/global/env.js +0 -11
  82. package/global/import.js +0 -39
  83. package/global/index.js +0 -8
  84. package/helper/index.js +0 -79
  85. package/loader/index.js +0 -6
  86. package/loader/loader.js +0 -138
  87. package/middleware/compress.js +0 -185
  88. package/middleware/setBody.js +0 -32
  89. package/realtime/index.js +0 -7
  90. package/realtime/sse.js +0 -424
  91. package/realtime/websocket.js +0 -540
  92. package/schedule/index.js +0 -6
  93. package/schedule/schedule.js +0 -491
package/doc/Service.md CHANGED
@@ -1,566 +1,240 @@
1
- # Service 数据库服务基类文档
2
- ## 一、模块概述
3
- `Service` 类是基于 `Container` 扩展的数据库服务基类,封装了常用的数据库操作方法,包括增删改查、分页查询、事务处理、软删除、关联查询等核心能力。该类自动处理日期字段格式化、数据库连接校验,并提供统一的返回格式,简化业务层数据库操作逻辑。
4
-
5
- ## 二、依赖说明
6
- - 继承自 `Container` 基类(`./Container.js`)
7
- - 引入日期字段格式化工具 `formatDateFields`(`../helper/time.js`)
8
- - 依赖全局对象 `Chan`:
9
- - `Chan.db`/`Chan.dbManager`:数据库连接实例/连接管理器
10
- - `Chan.config`:系统配置(包含分页、限制条数等配置项)
11
-
12
- ## 三、类构造与初始化
13
- ### 3.1 构造函数
14
- #### 函数签名
15
- ```javascript
16
- constructor(tableName = null, dbName = null)
17
- ```
18
- #### 参数说明
19
- | 参数名 | 类型 | 必填 | 默认值 | 说明 |
20
- |--------|------|------|--------|------|
21
- | tableName | string | 否 | null | 数据库表名,子类实例化时指定 |
22
- | dbName | string | 否 | null | 数据库连接名称,不传则使用默认数据库连接 |
23
-
24
- #### 初始化逻辑
25
- 1. 调用父类 `Container` 构造函数,指定组件类型为 `service`;
26
- 2. 根据 `dbName` 获取对应数据库连接(优先),否则使用默认连接 `Chan.db`;
27
- 3. 初始化日期字段列表 `_dateFields`,用于自动格式化日期字符串;
28
- 4. 若子类定义 `on` 方法,自动执行以注册事件监听器。
29
-
30
- ### 3.2 内置属性
31
- | 属性名 | 类型 | 说明 |
32
- |--------|------|------|
33
- | _dateFields | Array<string> | 需自动格式化的日期字段列表(包含下划线/驼峰命名的常见日期字段) |
34
- | pageSize(getter) | number | 每页默认记录数,优先读取 `Chan.config.PAGE_SIZE`,默认20 |
35
- | limit(getter) | number | 查询最大限制条数,优先读取 `Chan.config.LIMIT_MAX`,默认300 |
36
-
37
- ## 四、私有方法
38
- ### 4.1 _checkDB
39
- #### 功能描述
40
- 校验数据库连接是否可用,不可用时抛出异常。
41
- #### 函数签名
42
- ```javascript
43
- _checkDB()
44
- ```
45
- #### 异常抛出
46
- - 类型:`Error`
47
- - 消息:`Database connection not available`
48
-
49
- ### 4.2 _formatDateFields
50
- #### 功能描述
51
- 自动将日期字符串转换为数据库可识别的 `Date` 类型,仅处理 `_dateFields` 中的字段。
52
- #### 函数签名
53
- ```javascript
54
- _formatDateFields(data) => Object
55
- ```
56
- #### 参数说明
57
- | 参数名 | 类型 | 必填 | 说明 |
58
- |--------|------|------|------|
59
- | data | Object | 是 | 待格式化的数据对象 |
1
+ # Service 服务基类
60
2
 
61
- #### 返回值
62
- | 类型 | 说明 |
63
- |------|------|
64
- | Object | 格式化后的数据对象(原对象深拷贝,避免修改源数据) |
3
+ ## 概述
65
4
 
66
- #### 处理逻辑
67
- 1. 非对象类型直接返回;
68
- 2. 遍历 `_dateFields` 中的字段,若字段值为有效日期字符串,转换为 `Date` 对象;
69
- 3. 转换失败时打印错误日志,不中断流程。
5
+ `Service` 是纯业务逻辑层基类,继承自 `Container` 容器类。**不包含任何数据库 CRUD 方法**,仅作为业务逻辑的载体。
70
6
 
71
- ### 4.3 _buildBaseQuery
72
- #### 功能描述
73
- 构建基础查询器(Knex 查询构建器),整合查询条件、排序、字段筛选。
74
- #### 函数签名
75
- ```javascript
76
- _buildBaseQuery({ query = {}, sort = {}, fields = [] } = {}) => Object
77
- ```
78
- #### 参数说明
79
- | 参数名 | 类型 | 必填 | 默认值 | 说明 |
80
- |--------|------|------|--------|------|
81
- | query | Object | 否 | {} | 查询条件(Knex `where` 格式) |
82
- | sort | Object | 否 | {} | 排序条件(键:字段名,值:asc/desc) |
83
- | fields | Array<string> | 否 | [] | 查询字段列表(为空则查询所有字段) |
84
-
85
- #### 返回值
86
- | 类型 | 说明 |
87
- |------|------|
88
- | Object | Knex 查询构建器实例 |
89
-
90
- #### 处理逻辑
91
- 1. 先调用 `_checkDB` 校验连接;
92
- 2. 初始化查询器,指定操作表名;
93
- 3. 依次添加 `where` 条件、字段筛选、排序规则(排序方向自动兼容大小写,默认升序)。
94
-
95
- ## 五、核心操作方法
96
- ### 5.1 all - 查询所有记录
97
- #### 功能描述
98
- 查询符合条件的所有记录,自动格式化日期字段。
99
- #### 函数签名
100
- ```javascript
101
- async all({ query = {}, sort = {}, fields = [] } = {}) => Promise<Array>
102
- ```
103
- #### 参数说明
104
- 同 `_buildBaseQuery` 方法参数。
105
- #### 返回值
106
- | 类型 | 说明 |
107
- |------|------|
108
- | Promise<Array> | 查询结果数组,日期字段已格式化 |
7
+ 业务子类可注入多个 `Repository` 实例,进行跨表事务编排。
8
+
9
+ ## 继承关系
109
10
 
110
- ### 5.2 find - 分页查询(偏移量模式)
111
- #### 功能描述
112
- 基于偏移量和限制条数的分页查询,返回结构化结果。
113
- #### 函数签名
114
- ```javascript
115
- async find({ query = {}, sort = {}, fields = [], limit, offset } = {}) => Promise<Object>
116
- ```
117
- #### 参数说明
118
- | 参数名 | 类型 | 必填 | 默认值 | 说明 |
119
- |--------|------|------|--------|------|
120
- | query | Object | 否 | {} | 查询条件 |
121
- | sort | Object | 否 | {} | 排序条件 |
122
- | fields | Array<string> | 否 | [] | 查询字段 |
123
- | limit | number | 否 | - | 限制返回条数 |
124
- | offset | number | 否 | - | 偏移量(从0开始) |
125
-
126
- #### 返回值
127
- | 类型 | 结构 | 说明 |
128
- |------|------|------|
129
- | Promise<Object> | `{ success: true, code: 200, msg: '查询成功', data: Array }` | data 为查询结果数组,日期字段已格式化 |
130
-
131
- ### 5.3 findOne - 查询单条记录
132
- #### 功能描述
133
- 根据条件查询单条记录,无结果时返回明确的失败状态。
134
- #### 函数签名
135
- ```javascript
136
- async findOne({ query = {}, fields = [] } = {}) => Promise<Object>
137
- ```
138
- #### 参数说明
139
- | 参数名 | 类型 | 必填 | 默认值 | 说明 |
140
- |--------|------|------|--------|------|
141
- | query | Object | 否 | {} | 查询条件(建议唯一条件) |
142
- | fields | Array<string> | 否 | [] | 查询字段 |
143
-
144
- #### 返回值
145
- | 场景 | 返回结构 |
146
- |------|----------|
147
- | 有结果 | `{ success: true, code: 200, msg: '查询成功', data: Object }` |
148
- | 无结果 | `{ success: false, code: 404, msg: '记录不存在', data: null }` |
149
-
150
- ### 5.4 findById - 根据ID查询单条记录
151
- #### 功能描述
152
- 简化根据主键ID查询单条记录的逻辑,参数更简洁。
153
- #### 函数签名
154
- ```javascript
155
- async findById(id, { fields = [] } = {}) => Promise<Object>
156
- ```
157
- #### 参数说明
158
- | 参数名 | 类型 | 必填 | 默认值 | 说明 |
159
- |--------|------|------|--------|------|
160
- | id | number/string | 是 | - | 记录主键ID |
161
- | fields | Array<string> | 否 | [] | 查询字段 |
162
-
163
- #### 返回值
164
- 同 `findOne` 方法。
165
-
166
- ### 5.5 insert - 插入单条记录
167
- #### 功能描述
168
- 插入单条记录,自动格式化日期字段,返回插入结果。
169
- #### 函数签名
170
- ```javascript
171
- async insert(data = {}) => Promise<Object>
172
- ```
173
- #### 参数说明
174
- | 参数名 | 类型 | 必填 | 默认值 | 说明 |
175
- |--------|------|------|--------|------|
176
- | data | Object | 否 | {} | 插入的数据对象 |
177
-
178
- #### 返回值
179
- | 场景 | 返回结构 |
180
- |------|----------|
181
- | 参数为空 | `{ success: false, code: 400, msg: '参数缺失', data: {} }` |
182
- | 插入成功 | `{ success: true, code: 200, msg: '插入成功', data: { insertId: number, affectedRows: number } }` |
183
-
184
- ### 5.6 insertMany - 批量插入记录
185
- #### 功能描述
186
- 批量插入多条记录,自动格式化每条记录的日期字段。
187
- #### 函数签名
188
- ```javascript
189
- async insertMany(records = []) => Promise<Object>
190
- ```
191
- #### 参数说明
192
- | 参数名 | 类型 | 必填 | 默认值 | 说明 |
193
- |--------|------|------|--------|------|
194
- | records | Array<Object> | 否 | [] | 待插入的记录数组 |
195
-
196
- #### 返回值
197
- | 场景 | 返回结构 |
198
- |------|----------|
199
- | 参数为空 | `{ success: false, code: 400, msg: '参数缺失', data: {} }` |
200
- | 插入成功 | `{ success: true, code: 200, msg: '批量插入成功', data: { insertIds: Array<number>, affectedRows: number } }` |
201
-
202
- ### 5.7 delete - 根据条件删除记录
203
- #### 功能描述
204
- 根据自定义条件删除记录,返回受影响行数。
205
- #### 函数签名
206
- ```javascript
207
- async delete(query = {}) => Promise<Object>
208
- ```
209
- #### 参数说明
210
- | 参数名 | 类型 | 必填 | 默认值 | 说明 |
211
- |--------|------|------|--------|------|
212
- | query | Object | 否 | {} | 删除条件(Knex `where` 格式) |
213
-
214
- #### 返回值
215
- | 场景 | 返回结构 |
216
- |------|----------|
217
- | 条件为空 | `{ success: false, code: 400, msg: '参数缺失', data: {} }` |
218
- | 删除成功 | `{ success: true, code: 200, msg: '删除成功', data: { affectedRows: number } }` |
219
-
220
- ### 5.8 deleteById - 根据ID删除记录
221
- #### 功能描述
222
- 简化根据主键ID删除记录的逻辑。
223
- #### 函数签名
224
- ```javascript
225
- async deleteById(id) => Promise<Object>
226
- ```
227
- #### 参数说明
228
- | 参数名 | 类型 | 必填 | 说明 |
229
- |--------|------|------|------|
230
- | id | number/string | 是 | 记录主键ID |
231
-
232
- #### 返回值
233
- 同 `delete` 方法。
234
-
235
- ### 5.9 updateByQuery - 根据条件更新记录
236
- #### 功能描述
237
- 根据自定义条件更新记录,自动格式化日期字段,返回受影响行数。
238
- #### 函数签名
239
- ```javascript
240
- async updateByQuery({ query, data } = {}) => Promise<Object>
241
11
  ```
242
- #### 参数说明
243
- | 参数名 | 类型 | 必填 | 说明 |
244
- |--------|------|------|------|
245
- | query | Object | 是 | 更新条件(Knex `where` 格式) |
246
- | data | Object | 是 | 更新的数据对象 |
247
-
248
- #### 返回值
249
- | 场景 | 返回结构 |
250
- |------|----------|
251
- | 参数无效(条件/数据为空) | `{ success: false, code: 400, msg: '参数无效', data: {} }` |
252
- | 更新成功 | `{ success: true, code: 200, msg: '更新成功', data: { affectedRows: number } }` |
253
-
254
- ### 5.10 updateById - 根据ID更新记录
255
- #### 功能描述
256
- 根据主键ID更新记录,返回更新后的完整记录。
257
- #### 函数签名
258
- ```javascript
259
- async updateById(id, data = {}) => Promise<Object>
12
+ Container <── Service
260
13
  ```
261
- #### 参数说明
262
- | 参数名 | 类型 | 必填 | 默认值 | 说明 |
263
- |--------|------|------|--------|------|
264
- | id | number/string | 是 | - | 记录主键ID |
265
- | data | Object | 否 | {} | 更新的数据对象 |
266
-
267
- #### 返回值
268
- | 场景 | 返回结构 |
269
- |------|----------|
270
- | 参数无效(ID/数据为空) | `{ success: false, code: 400, msg: '参数无效', data: {} }` |
271
- | 更新成功 | `{ success: true, code: 200, msg: '更新成功', data: Object }` |
272
-
273
- ### 5.11 updateMany - 批量更新记录(事务)
274
- #### 功能描述
275
- 基于事务的批量更新,确保所有更新操作原子性(要么全部成功,要么全部回滚)。
276
- #### 函数签名
277
- ```javascript
278
- async updateMany(updates = []) => Promise<Object>
279
- ```
280
- #### 参数说明
281
- | 参数名 | 类型 | 必填 | 默认值 | 说明 |
282
- |--------|------|------|--------|------|
283
- | updates | Array<Object> | 否 | [] | 批量更新配置,每项结构:`{ query: Object, data: Object }` |
284
-
285
- #### 返回值
286
- | 场景 | 返回结构 |
287
- |------|----------|
288
- | 参数无效(非数组/空数组) | `{ success: false, code: 400, msg: '参数缺失', data: {} }` |
289
- | 单条条件为空 | `{ success: false, code: 400, msg: '参数无效:批量更新不允许空条件', data: {} }` |
290
- | 更新成功 | `{ success: true, code: 200, msg: '批量更新成功', data: { affectedRows: number } }` |
291
-
292
- #### 事务逻辑
293
- 1. 开启数据库事务;
294
- 2. 遍历更新配置,逐条执行更新;
295
- 3. 任意一条更新条件为空时,回滚事务并返回失败;
296
- 4. 全部执行完成后提交事务,返回总受影响行数。
297
-
298
- ### 5.12 query - 分页查询(页码模式)
299
- #### 功能描述
300
- 基于页码的分页查询,自动计算偏移量,返回包含分页信息的结构化结果。
301
- #### 函数签名
14
+
15
+ ## 构造函数
16
+
302
17
  ```javascript
303
- async query({ current = 1, pageSize = 10, query = {}, sort = {}, field = [] }) => Promise<Object>
18
+ constructor()
304
19
  ```
305
- #### 参数说明
306
- | 参数名 | 类型 | 必填 | 默认值 | 说明 |
307
- |--------|------|------|--------|------|
308
- | current | number | 否 | 1 | 当前页码 |
309
- | pageSize | number | 否 | 10 | 每页条数(自动限制在1~limit之间) |
310
- | query | Object | 否 | {} | 查询条件 |
311
- | sort | Object | 否 | {} | 排序条件 |
312
- | field | Array<string> | 否 | [] | 查询字段 |
313
-
314
- #### 返回值
20
+
21
+ 调用父类 `Container` 构造函数,指定组件类型为 `'service'`。
22
+
23
+ ## 设计原则
24
+
25
+ 1. **无表绑定**:Service 不直接操作数据库表
26
+ 2. **无 CRUD 方法**:所有数据操作委托给 Repository
27
+ 3. **业务编排**:负责组合多个 Repository 完成复杂业务逻辑
28
+ 4. **事务管理**:可跨 Repository 进行事务编排
29
+
30
+ ## 使用示例
31
+
32
+ ### 基础示例
33
+
315
34
  ```javascript
316
- {
317
- success: true,
318
- code: 200,
319
- msg: '查询成功',
320
- data: {
321
- list: Array, // 查询结果列表
322
- total: number, // 总记录数
323
- current: number, // 当前页码
324
- pageSize: number, // 实际每页条数
325
- totalPages: number // 总页数
35
+ import { Service } from 'chanjs';
36
+ import UserRepo from '../repository/UserRepo.js';
37
+ import OrderRepo from '../repository/OrderRepo.js';
38
+
39
+ export default class UserService extends Service {
40
+ constructor() {
41
+ super();
42
+ this.userRepo = new UserRepo();
43
+ this.orderRepo = new OrderRepo();
44
+ }
45
+
46
+ // 查询用户及其订单
47
+ async getUserWithOrders(userId) {
48
+ const user = await this.userRepo.findById(userId);
49
+ if (!user.success) {
50
+ return user;
51
+ }
52
+
53
+ const orders = await this.orderRepo.all({
54
+ query: { userId }
55
+ });
56
+
57
+ return this.success({
58
+ data: {
59
+ user: user.data,
60
+ orders: orders.data
61
+ }
62
+ });
326
63
  }
327
64
  }
328
65
  ```
329
66
 
330
- ### 5.13 count - 统计记录数
331
- #### 功能描述
332
- 根据条件统计记录总数。
333
- #### 函数签名
334
- ```javascript
335
- async count(query = {}) => Promise<Object>
336
- ```
337
- #### 参数说明
338
- | 参数名 | 类型 | 必填 | 默认值 | 说明 |
339
- |--------|------|------|--------|------|
340
- | query | Object | 否 | {} | 统计条件 |
67
+ ### 跨表事务示例
341
68
 
342
- #### 返回值
343
69
  ```javascript
344
- {
345
- success: true,
346
- code: 200,
347
- msg: '统计成功',
348
- data: { count: number } // 统计结果
349
- }
350
- ```
70
+ import { Service } from 'chanjs';
71
+ import UserRepo from '../repository/UserRepo.js';
72
+ import WalletRepo from '../repository/WalletRepo.js';
351
73
 
352
- ### 5.14 exists - 检查记录是否存在
353
- #### 功能描述
354
- 根据条件检查是否存在匹配记录。
355
- #### 函数签名
356
- ```javascript
357
- async exists(query = {}) => Promise<Object>
358
- ```
359
- #### 参数说明
360
- | 参数名 | 类型 | 必填 | 默认值 | 说明 |
361
- |--------|------|------|--------|------|
362
- | query | Object | 否 | {} | 检查条件 |
74
+ export default class PaymentService extends Service {
75
+ constructor() {
76
+ super();
77
+ this.userRepo = new UserRepo();
78
+ this.walletRepo = new WalletRepo();
79
+ }
363
80
 
364
- #### 返回值
365
- ```javascript
366
- {
367
- success: true,
368
- code: 200,
369
- msg: '检查成功',
370
- data: { exists: boolean } // 是否存在匹配记录
81
+ // 用户充值(跨表事务)
82
+ async recharge(userId, amount) {
83
+ const trx = await this.db.transaction();
84
+
85
+ try {
86
+ // 更新用户余额
87
+ await this.userRepo.updateById(userId, {
88
+ balance: this.db.raw(`balance + ${amount}`)
89
+ }, trx);
90
+
91
+ // 记录充值流水
92
+ await this.walletRepo.insert({
93
+ userId,
94
+ amount,
95
+ type: 'recharge',
96
+ createdAt: new Date()
97
+ }, trx);
98
+
99
+ await trx.commit();
100
+ return this.success({ msg: '充值成功' });
101
+ } catch (err) {
102
+ await trx.rollback();
103
+ return this.fail({
104
+ msg: '充值失败',
105
+ code: 500
106
+ });
107
+ }
108
+ }
371
109
  }
372
110
  ```
373
111
 
374
- ### 5.15 join - 关联查询
375
- #### 功能描述
376
- 实现两表内连接查询,支持条件、排序、字段筛选。
377
- #### 函数签名
378
- ```javascript
379
- async join({ joinTable, localField, foreignField, fields = ["*"], query = {}, sort = {} }) => Promise<Object>
380
- ```
381
- #### 参数说明
382
- | 参数名 | 类型 | 必填 | 默认值 | 说明 |
383
- |--------|------|------|--------|------|
384
- | joinTable | string | 是 | - | 关联表名 |
385
- | localField | string | 是 | - | 主表关联字段 |
386
- | foreignField | string | 是 | - | 关联表关联字段 |
387
- | fields | Array<string> | 否 | ["*"] | 查询字段(可指定表别名,如 `table1.field1`) |
388
- | query | Object | 否 | {} | 查询条件 |
389
- | sort | Object | 否 | {} | 排序条件 |
390
-
391
- #### 返回值
112
+ ### 复杂业务逻辑示例
113
+
392
114
  ```javascript
393
- {
394
- success: true,
395
- code: 200,
396
- msg: '查询成功',
397
- data: Array // 关联查询结果列表
115
+ import { Service } from 'chanjs';
116
+ import UserRepo from '../repository/UserRepo.js';
117
+ import OrderRepo from '../repository/OrderRepo.js';
118
+ import ProductRepo from '../repository/ProductRepo.js';
119
+
120
+ export default class OrderService extends Service {
121
+ constructor() {
122
+ super();
123
+ this.userRepo = new UserRepo();
124
+ this.orderRepo = new OrderRepo();
125
+ this.productRepo = new ProductRepo();
126
+ }
127
+
128
+ // 创建订单
129
+ async createOrder(userId, products) {
130
+ // 1. 校验用户
131
+ const user = await this.userRepo.findById(userId);
132
+ if (!user.success) {
133
+ return this.fail({ msg: '用户不存在' });
134
+ }
135
+
136
+ // 2. 校验商品库存
137
+ for (const item of products) {
138
+ const product = await this.productRepo.findById(item.productId);
139
+ if (!product.success || product.data.stock < item.quantity) {
140
+ return this.fail({
141
+ msg: `商品 ${product.data.name} 库存不足`
142
+ });
143
+ }
144
+ }
145
+
146
+ // 3. 计算订单金额
147
+ const totalAmount = await this.calculateTotal(products);
148
+
149
+ // 4. 创建订单
150
+ const order = await this.orderRepo.insert({
151
+ userId,
152
+ totalAmount,
153
+ status: 'pending',
154
+ createdAt: new Date()
155
+ });
156
+
157
+ // 5. 扣减库存
158
+ await this.deductStock(products);
159
+
160
+ return this.success({
161
+ data: { orderId: order.data.insertId },
162
+ msg: '订单创建成功'
163
+ });
164
+ }
165
+
166
+ // 计算订单总额
167
+ async calculateTotal(products) {
168
+ let total = 0;
169
+ for (const item of products) {
170
+ const product = await this.productRepo.findById(item.productId);
171
+ total += product.data.price * item.quantity;
172
+ }
173
+ return total;
174
+ }
175
+
176
+ // 扣减库存
177
+ async deductStock(products) {
178
+ for (const item of products) {
179
+ await this.productRepo.updateById(item.productId, {
180
+ stock: this.db.raw(`stock - ${item.quantity}`)
181
+ });
182
+ }
183
+ }
398
184
  }
399
185
  ```
400
186
 
401
- ### 5.16 deleteMany - 批量删除(ID数组)
402
- #### 功能描述
403
- 根据ID数组批量删除记录。
404
- #### 函数签名
405
- ```javascript
406
- async deleteMany(ids = []) => Promise<Object>
407
- ```
408
- #### 参数说明
409
- | 参数名 | 类型 | 必填 | 默认值 | 说明 |
410
- |--------|------|------|--------|------|
411
- | ids | Array<number/string> | 否 | [] | 待删除记录的ID数组 |
412
-
413
- #### 返回值
414
- | 场景 | 返回结构 |
415
- |------|----------|
416
- | ID数组为空 | `{ success: false, code: 400, msg: '参数缺失', data: {} }` |
417
- | 删除成功 | `{ success: true, code: 200, msg: '删除成功', data: { affectedRows: number } }` |
418
-
419
- ### 5.17 softDelete - 软删除
420
- #### 功能描述
421
- 设置 `deleted_at` 字段为当前时间,实现逻辑删除(非物理删除)。
422
- #### 函数签名
423
- ```javascript
424
- async softDelete(id) => Promise<Object>
425
- ```
426
- #### 参数说明
427
- | 参数名 | 类型 | 必填 | 说明 |
428
- |--------|------|------|------|
429
- | id | number/string | 是 | 记录主键ID |
430
-
431
- #### 返回值
432
- | 场景 | 返回结构 |
433
- |------|----------|
434
- | 参数为空 | `{ msg: '参数缺失' }` |
435
- | 删除成功 | `{ affectedRows: number }` |
436
-
437
- ### 5.18 restore - 恢复软删除记录
438
- #### 功能描述
439
- 将 `deleted_at` 字段置为 `null`,恢复软删除的记录。
440
- #### 函数签名
441
- ```javascript
442
- async restore(id) => Promise<Object>
443
- ```
444
- #### 参数说明
445
- | 参数名 | 类型 | 必填 | 说明 |
446
- |--------|------|------|------|
447
- | id | number/string | 是 | 记录主键ID |
448
-
449
- #### 返回值
450
- | 场景 | 返回结构 |
451
- |------|----------|
452
- | 参数为空 | `{ msg: '参数缺失' }` |
453
- | 恢复成功 | `{ affectedRows: number }` |
454
-
455
- ### 5.19 findTrashed - 查询软删除记录
456
- #### 功能描述
457
- 查询已被软删除的记录(`deleted_at` 不为空)。
458
- #### 函数签名
459
- ```javascript
460
- async findTrashed({ query = {}, fields = [], limit = 20, offset = 0 } = {}) => Promise<Array>
461
- ```
462
- #### 参数说明
463
- | 参数名 | 类型 | 必填 | 默认值 | 说明 |
464
- |--------|------|------|--------|------|
465
- | query | Object | 否 | {} | 额外查询条件 |
466
- | fields | Array<string> | 否 | [] | 查询字段 |
467
- | limit | number | 否 | 20 | 限制条数 |
468
- | offset | number | 否 | 0 | 偏移量 |
469
-
470
- #### 返回值
471
- | 类型 | 说明 |
472
- |------|------|
473
- | Promise<Array> | 软删除记录列表,按 `deleted_at` 降序排列 |
187
+ ## 访问全局资源
474
188
 
475
- ### 5.20 forceDelete - 强制删除软删除记录
476
- #### 功能描述
477
- 物理删除指定天数前被软删除的记录,用于清理历史数据。
478
- #### 函数签名
479
- ```javascript
480
- async forceDelete(days = 30) => Promise<Object>
481
- ```
482
- #### 参数说明
483
- | 参数名 | 类型 | 必填 | 默认值 | 说明 |
484
- |--------|------|------|--------|------|
485
- | days | number | 否 | 30 | 天数(删除N天前的软删除记录) |
189
+ Service 继承自 Container,可访问以下全局资源:
486
190
 
487
- #### 返回值
488
- | 类型 | 说明 |
191
+ | 属性 | 说明 |
489
192
  |------|------|
490
- | Promise<Object> | `{ affectedRows: number }` - 受影响的行数 |
193
+ | `this.app` | 全局应用实例 |
194
+ | `this.config` | 全局配置对象 |
195
+ | `this.db` | 默认数据库连接 |
196
+ | `this.paths` | 路径工具对象 |
491
197
 
492
- ### 5.21 stats - 统计总数与今日新增
493
- #### 功能描述
494
- 统计表中总记录数和今日新增记录数(按 `created_at` 字段)。
495
- #### 函数签名
496
- ```javascript
497
- async stats() => Promise<Object>
498
- ```
499
- #### 返回值
500
- | 类型 | 结构 |
501
- |------|------|
502
- | Promise<Object> | `{ total: number, today: number }` - total为总记录数,today为今日新增数 |
198
+ ### 示例
503
199
 
504
- ## 六、使用示例
505
- ### 6.1 基础使用(子类继承)
506
200
  ```javascript
507
- // UserService.js
508
- import Service from './Service.js';
509
-
510
- class UserService extends Service {
511
- constructor() {
512
- // 指定表名和数据库连接名
513
- super('user', 'default');
201
+ export default class ConfigService extends Service {
202
+ async getAppName() {
203
+ // 访问全局配置
204
+ const appName = this.config.APP_NAME;
205
+ return this.success({ data: appName });
514
206
  }
515
207
 
516
- // 自定义业务方法
517
- async findByUsername(username) {
518
- return this.findOne({ query: { username } });
208
+ async getDbVersion() {
209
+ // 访问数据库连接
210
+ const [result] = await this.db.raw('SELECT VERSION() as version');
211
+ return this.success({ data: result[0].version });
519
212
  }
520
213
  }
521
-
522
- export default new UserService();
523
214
  ```
524
215
 
525
- ### 6.2 调用示例
216
+ ## 动态加载组件
217
+
218
+ Service 可使用 `get` 方法动态加载其他 Service 或 Repository:
219
+
526
220
  ```javascript
527
- import userService from './UserService.js';
528
-
529
- // 1. 查询用户列表(分页)
530
- const userPage = await userService.query({
531
- current: 1,
532
- pageSize: 10,
533
- query: { status: 1 },
534
- sort: { create_at: 'desc' },
535
- field: ['id', 'username', 'email']
536
- });
537
-
538
- // 2. 新增用户
539
- const insertRes = await userService.insert({
540
- username: 'test',
541
- email: 'test@example.com',
542
- created_at: '2024-01-01 12:00:00'
543
- });
544
-
545
- // 3. 软删除用户
546
- await userService.softDelete(1);
547
-
548
- // 4. 恢复软删除用户
549
- await userService.restore(1);
550
-
551
- // 5. 关联查询(用户-订单)
552
- const userOrder = await userService.join({
553
- joinTable: 'order',
554
- localField: 'id',
555
- foreignField: 'user_id',
556
- fields: ['user.username', 'order.order_no'],
557
- query: { user.status: 1 }
558
- });
221
+ export default class OrderService extends Service {
222
+ async processOrder(orderId) {
223
+ // 动态加载 UserService
224
+ const userService = await this.get('user', 'UserService');
225
+
226
+ // 调用 UserService 方法
227
+ const user = await userService.getUserById(1);
228
+
229
+ // 业务逻辑...
230
+ }
231
+ }
559
232
  ```
560
233
 
561
- ## 七、注意事项
562
- 1. 所有方法均依赖 `Chan` 全局对象,需确保初始化时已配置数据库连接和相关配置项;
563
- 2. 日期字段格式化仅处理 `_dateFields` 中的字段,若需扩展可在子类中重写该属性;
564
- 3. 事务操作(`updateMany`)需数据库支持事务,否则会抛出异常;
565
- 4. 分页查询 `query` 方法会自动限制 `pageSize` 不超过 `limit`,避免大数据量查询;
566
- 5. 软删除相关方法依赖 `deleted_at` 字段,需确保表结构中包含该字段。
234
+ ## 注意事项
235
+
236
+ 1. Service 不包含 CRUD 方法,数据操作请使用 Repository
237
+ 2. 建议在 Service 中注入所需的 Repository 实例
238
+ 3. 跨表事务需在 Service 层统一管理
239
+ 4. Service 文件应放在 `app/modules/{moduleName}/service/` 目录下
240
+ 5. 导出时使用 `export default` 导出实例或类