chanjs 2.7.4 → 2.7.6
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/USAGE.md +533 -0
- package/config/index.js +37 -6
- package/core/App.js +166 -0
- package/core/BaseComponent.js +27 -0
- package/core/Container.js +68 -0
- package/core/Controller.js +29 -0
- package/core/Database.js +93 -0
- package/core/Repository.js +323 -0
- package/core/Service.js +11 -0
- package/core/bootstrap/error-handler.js +101 -0
- package/core/bootstrap/hook-runner.js +64 -0
- package/core/bootstrap/middleware.js +35 -0
- package/core/bootstrap/router-loader.js +53 -0
- package/core/errors.js +251 -0
- package/core/loader.js +89 -0
- package/core/registry.js +17 -0
- package/doc/Cache.md +279 -106
- package/doc/Common.md +590 -134
- package/doc/Controller.md +166 -95
- package/doc/Help.md +299 -698
- package/doc/QuickStart.md +116 -0
- package/doc/Repository.md +560 -0
- package/doc/Service.md +201 -527
- package/index.js +61 -37
- package/middleware/body.js +17 -0
- package/middleware/cookie.js +7 -15
- package/middleware/cors.js +9 -27
- package/middleware/favicon.js +15 -17
- package/middleware/header.js +15 -16
- package/middleware/index.js +11 -11
- package/middleware/log.js +26 -56
- package/middleware/static.js +15 -28
- package/middleware/template.js +75 -115
- package/middleware/validate.js +79 -0
- package/middleware/waf.js +176 -197
- package/package.json +9 -2
- package/response/code.js +73 -0
- package/response/index.js +9 -6
- package/response/response.js +82 -236
- package/security/checker.js +26 -74
- package/security/index.js +4 -9
- package/security/jwt.js +84 -139
- package/security/keywords.js +33 -137
- package/security/rate-limit.js +38 -80
- package/security/sign.js +83 -176
- package/security/xss-filter.js +21 -53
- package/storage/cache.js +58 -198
- package/storage/index.js +3 -6
- package/storage/redis.js +124 -181
- package/storage/store.js +163 -188
- package/utils/data-parse.js +42 -186
- package/utils/file.js +73 -244
- package/utils/filter.js +22 -25
- package/utils/html.js +49 -33
- package/utils/index.js +20 -7
- package/utils/ip.js +31 -71
- package/utils/logger.js +117 -0
- package/utils/pages.js +55 -0
- package/utils/paths.js +18 -0
- package/utils/request.js +95 -136
- package/utils/signal.js +87 -0
- package/utils/time.js +33 -75
- package/utils/tree.js +112 -104
- package/App.js +0 -533
- package/base/Aop.js +0 -195
- package/base/Container.js +0 -161
- package/base/Controller.js +0 -65
- package/base/Database.js +0 -133
- package/base/Event.js +0 -61
- package/base/Repository.js +0 -644
- package/common/api.js +0 -35
- package/common/code.js +0 -52
- package/common/email.js +0 -191
- package/common/index.js +0 -5
- package/common/pages.js +0 -120
- package/common/utils.js +0 -73
- package/config/code.js +0 -166
- package/config/paths.js +0 -60
- package/doc/Aop.md +0 -269
- package/doc/Email.md +0 -114
- package/doc/Event.md +0 -232
- package/global/env.js +0 -11
- package/global/import.js +0 -39
- package/global/index.js +0 -8
- package/helper/index.js +0 -79
- package/loader/index.js +0 -6
- package/loader/loader.js +0 -138
- package/middleware/compress.js +0 -185
- package/middleware/setBody.js +0 -32
- package/realtime/index.js +0 -7
- package/realtime/sse.js +0 -424
- package/realtime/websocket.js +0 -540
- package/schedule/index.js +0 -6
- 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
|
-
|
|
72
|
-
|
|
73
|
-
|
|
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
|
-
|
|
18
|
+
constructor()
|
|
304
19
|
```
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
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
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
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
|
-
###
|
|
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
|
-
|
|
346
|
-
|
|
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
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
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
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
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
|
-
###
|
|
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
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
|
193
|
+
| `this.app` | 全局应用实例 |
|
|
194
|
+
| `this.config` | 全局配置对象 |
|
|
195
|
+
| `this.db` | 默认数据库连接 |
|
|
196
|
+
| `this.paths` | 路径工具对象 |
|
|
491
197
|
|
|
492
|
-
###
|
|
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
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
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
|
-
|
|
518
|
-
|
|
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
|
-
|
|
216
|
+
## 动态加载组件
|
|
217
|
+
|
|
218
|
+
Service 可使用 `get` 方法动态加载其他 Service 或 Repository:
|
|
219
|
+
|
|
526
220
|
```javascript
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
//
|
|
530
|
-
const
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
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
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
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` 导出实例或类
|