# Service 数据库服务基类文档
## 一、模块概述
`Service` 类是基于 `Container` 扩展的数据库服务基类，封装了常用的数据库操作方法，包括增删改查、分页查询、事务处理、软删除、关联查询等核心能力。该类自动处理日期字段格式化、数据库连接校验，并提供统一的返回格式，简化业务层数据库操作逻辑。

## 二、依赖说明
- 继承自 `Container` 基类（`./Container.js`）
- 引入日期字段格式化工具 `formatDateFields`（`../helper/time.js`）
- 依赖全局对象 `Chan`：
  - `Chan.db`/`Chan.dbManager`：数据库连接实例/连接管理器
  - `Chan.config`：系统配置（包含分页、限制条数等配置项）

## 三、类构造与初始化
### 3.1 构造函数
#### 函数签名
```javascript
constructor(tableName = null, dbName = null)
```
#### 参数说明
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|--------|------|------|--------|------|
| tableName | string | 否 | null | 数据库表名，子类实例化时指定 |
| dbName | string | 否 | null | 数据库连接名称，不传则使用默认数据库连接 |

#### 初始化逻辑
1. 调用父类 `Container` 构造函数，指定组件类型为 `service`；
2. 根据 `dbName` 获取对应数据库连接（优先），否则使用默认连接 `Chan.db`；
3. 初始化日期字段列表 `_dateFields`，用于自动格式化日期字符串；
4. 若子类定义 `on` 方法，自动执行以注册事件监听器。

### 3.2 内置属性
| 属性名 | 类型 | 说明 |
|--------|------|------|
| _dateFields | Array<string> | 需自动格式化的日期字段列表（包含下划线/驼峰命名的常见日期字段） |
| pageSize（getter） | number | 每页默认记录数，优先读取 `Chan.config.PAGE_SIZE`，默认20 |
| limit（getter） | number | 查询最大限制条数，优先读取 `Chan.config.LIMIT_MAX`，默认300 |

## 四、私有方法
### 4.1 _checkDB
#### 功能描述
校验数据库连接是否可用，不可用时抛出异常。
#### 函数签名
```javascript
_checkDB()
```
#### 异常抛出
- 类型：`Error`
- 消息：`Database connection not available`

### 4.2 _formatDateFields
#### 功能描述
自动将日期字符串转换为数据库可识别的 `Date` 类型，仅处理 `_dateFields` 中的字段。
#### 函数签名
```javascript
_formatDateFields(data) => Object
```
#### 参数说明
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| data | Object | 是 | 待格式化的数据对象 |

#### 返回值
| 类型 | 说明 |
|------|------|
| Object | 格式化后的数据对象（原对象深拷贝，避免修改源数据） |

#### 处理逻辑
1. 非对象类型直接返回；
2. 遍历 `_dateFields` 中的字段，若字段值为有效日期字符串，转换为 `Date` 对象；
3. 转换失败时打印错误日志，不中断流程。

### 4.3 _buildBaseQuery
#### 功能描述
构建基础查询器（Knex 查询构建器），整合查询条件、排序、字段筛选。
#### 函数签名
```javascript
_buildBaseQuery({ query = {}, sort = {}, fields = [] } = {}) => Object
```
#### 参数说明
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|--------|------|------|--------|------|
| query | Object | 否 | {} | 查询条件（Knex `where` 格式） |
| sort | Object | 否 | {} | 排序条件（键：字段名，值：asc/desc） |
| fields | Array<string> | 否 | [] | 查询字段列表（为空则查询所有字段） |

#### 返回值
| 类型 | 说明 |
|------|------|
| Object | Knex 查询构建器实例 |

#### 处理逻辑
1. 先调用 `_checkDB` 校验连接；
2. 初始化查询器，指定操作表名；
3. 依次添加 `where` 条件、字段筛选、排序规则（排序方向自动兼容大小写，默认升序）。

## 五、核心操作方法
### 5.1 all - 查询所有记录
#### 功能描述
查询符合条件的所有记录，自动格式化日期字段。
#### 函数签名
```javascript
async all({ query = {}, sort = {}, fields = [] } = {}) => Promise<Array>
```
#### 参数说明
同 `_buildBaseQuery` 方法参数。
#### 返回值
| 类型 | 说明 |
|------|------|
| Promise<Array> | 查询结果数组，日期字段已格式化 |

### 5.2 find - 分页查询（偏移量模式）
#### 功能描述
基于偏移量和限制条数的分页查询，返回结构化结果。
#### 函数签名
```javascript
async find({ query = {}, sort = {}, fields = [], limit, offset } = {}) => Promise<Object>
```
#### 参数说明
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|--------|------|------|--------|------|
| query | Object | 否 | {} | 查询条件 |
| sort | Object | 否 | {} | 排序条件 |
| fields | Array<string> | 否 | [] | 查询字段 |
| limit | number | 否 | - | 限制返回条数 |
| offset | number | 否 | - | 偏移量（从0开始） |

#### 返回值
| 类型 | 结构 | 说明 |
|------|------|------|
| Promise<Object> | `{ success: true, code: 200, msg: '查询成功', data: Array }` | data 为查询结果数组，日期字段已格式化 |

### 5.3 findOne - 查询单条记录
#### 功能描述
根据条件查询单条记录，无结果时返回明确的失败状态。
#### 函数签名
```javascript
async findOne({ query = {}, fields = [] } = {}) => Promise<Object>
```
#### 参数说明
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|--------|------|------|--------|------|
| query | Object | 否 | {} | 查询条件（建议唯一条件） |
| fields | Array<string> | 否 | [] | 查询字段 |

#### 返回值
| 场景 | 返回结构 |
|------|----------|
| 有结果 | `{ success: true, code: 200, msg: '查询成功', data: Object }` |
| 无结果 | `{ success: false, code: 404, msg: '记录不存在', data: null }` |

### 5.4 findById - 根据ID查询单条记录
#### 功能描述
简化根据主键ID查询单条记录的逻辑，参数更简洁。
#### 函数签名
```javascript
async findById(id, { fields = [] } = {}) => Promise<Object>
```
#### 参数说明
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|--------|------|------|--------|------|
| id | number/string | 是 | - | 记录主键ID |
| fields | Array<string> | 否 | [] | 查询字段 |

#### 返回值
同 `findOne` 方法。

### 5.5 insert - 插入单条记录
#### 功能描述
插入单条记录，自动格式化日期字段，返回插入结果。
#### 函数签名
```javascript
async insert(data = {}) => Promise<Object>
```
#### 参数说明
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|--------|------|------|--------|------|
| data | Object | 否 | {} | 插入的数据对象 |

#### 返回值
| 场景 | 返回结构 |
|------|----------|
| 参数为空 | `{ success: false, code: 400, msg: '参数缺失', data: {} }` |
| 插入成功 | `{ success: true, code: 200, msg: '插入成功', data: { insertId: number, affectedRows: number } }` |

### 5.6 insertMany - 批量插入记录
#### 功能描述
批量插入多条记录，自动格式化每条记录的日期字段。
#### 函数签名
```javascript
async insertMany(records = []) => Promise<Object>
```
#### 参数说明
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|--------|------|------|--------|------|
| records | Array<Object> | 否 | [] | 待插入的记录数组 |

#### 返回值
| 场景 | 返回结构 |
|------|----------|
| 参数为空 | `{ success: false, code: 400, msg: '参数缺失', data: {} }` |
| 插入成功 | `{ success: true, code: 200, msg: '批量插入成功', data: { insertIds: Array<number>, affectedRows: number } }` |

### 5.7 delete - 根据条件删除记录
#### 功能描述
根据自定义条件删除记录，返回受影响行数。
#### 函数签名
```javascript
async delete(query = {}) => Promise<Object>
```
#### 参数说明
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|--------|------|------|--------|------|
| query | Object | 否 | {} | 删除条件（Knex `where` 格式） |

#### 返回值
| 场景 | 返回结构 |
|------|----------|
| 条件为空 | `{ success: false, code: 400, msg: '参数缺失', data: {} }` |
| 删除成功 | `{ success: true, code: 200, msg: '删除成功', data: { affectedRows: number } }` |

### 5.8 deleteById - 根据ID删除记录
#### 功能描述
简化根据主键ID删除记录的逻辑。
#### 函数签名
```javascript
async deleteById(id) => Promise<Object>
```
#### 参数说明
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| id | number/string | 是 | 记录主键ID |

#### 返回值
同 `delete` 方法。

### 5.9 updateByQuery - 根据条件更新记录
#### 功能描述
根据自定义条件更新记录，自动格式化日期字段，返回受影响行数。
#### 函数签名
```javascript
async updateByQuery({ query, data } = {}) => Promise<Object>
```
#### 参数说明
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| query | Object | 是 | 更新条件（Knex `where` 格式） |
| data | Object | 是 | 更新的数据对象 |

#### 返回值
| 场景 | 返回结构 |
|------|----------|
| 参数无效（条件/数据为空） | `{ success: false, code: 400, msg: '参数无效', data: {} }` |
| 更新成功 | `{ success: true, code: 200, msg: '更新成功', data: { affectedRows: number } }` |

### 5.10 updateById - 根据ID更新记录
#### 功能描述
根据主键ID更新记录，返回更新后的完整记录。
#### 函数签名
```javascript
async updateById(id, data = {}) => Promise<Object>
```
#### 参数说明
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|--------|------|------|--------|------|
| id | number/string | 是 | - | 记录主键ID |
| data | Object | 否 | {} | 更新的数据对象 |

#### 返回值
| 场景 | 返回结构 |
|------|----------|
| 参数无效（ID/数据为空） | `{ success: false, code: 400, msg: '参数无效', data: {} }` |
| 更新成功 | `{ success: true, code: 200, msg: '更新成功', data: Object }` |

### 5.11 updateMany - 批量更新记录（事务）
#### 功能描述
基于事务的批量更新，确保所有更新操作原子性（要么全部成功，要么全部回滚）。
#### 函数签名
```javascript
async updateMany(updates = []) => Promise<Object>
```
#### 参数说明
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|--------|------|------|--------|------|
| updates | Array<Object> | 否 | [] | 批量更新配置，每项结构：`{ query: Object, data: Object }` |

#### 返回值
| 场景 | 返回结构 |
|------|----------|
| 参数无效（非数组/空数组） | `{ success: false, code: 400, msg: '参数缺失', data: {} }` |
| 单条条件为空 | `{ success: false, code: 400, msg: '参数无效：批量更新不允许空条件', data: {} }` |
| 更新成功 | `{ success: true, code: 200, msg: '批量更新成功', data: { affectedRows: number } }` |

#### 事务逻辑
1. 开启数据库事务；
2. 遍历更新配置，逐条执行更新；
3. 任意一条更新条件为空时，回滚事务并返回失败；
4. 全部执行完成后提交事务，返回总受影响行数。

### 5.12 query - 分页查询（页码模式）
#### 功能描述
基于页码的分页查询，自动计算偏移量，返回包含分页信息的结构化结果。
#### 函数签名
```javascript
async query({ current = 1, pageSize = 10, query = {}, sort = {}, field = [] }) => Promise<Object>
```
#### 参数说明
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|--------|------|------|--------|------|
| current | number | 否 | 1 | 当前页码 |
| pageSize | number | 否 | 10 | 每页条数（自动限制在1~limit之间） |
| query | Object | 否 | {} | 查询条件 |
| sort | Object | 否 | {} | 排序条件 |
| field | Array<string> | 否 | [] | 查询字段 |

#### 返回值
```javascript
{
  success: true,
  code: 200,
  msg: '查询成功',
  data: {
    list: Array, // 查询结果列表
    total: number, // 总记录数
    current: number, // 当前页码
    pageSize: number, // 实际每页条数
    totalPages: number // 总页数
  }
}
```

### 5.13 count - 统计记录数
#### 功能描述
根据条件统计记录总数。
#### 函数签名
```javascript
async count(query = {}) => Promise<Object>
```
#### 参数说明
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|--------|------|------|--------|------|
| query | Object | 否 | {} | 统计条件 |

#### 返回值
```javascript
{
  success: true,
  code: 200,
  msg: '统计成功',
  data: { count: number } // 统计结果
}
```

### 5.14 exists - 检查记录是否存在
#### 功能描述
根据条件检查是否存在匹配记录。
#### 函数签名
```javascript
async exists(query = {}) => Promise<Object>
```
#### 参数说明
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|--------|------|------|--------|------|
| query | Object | 否 | {} | 检查条件 |

#### 返回值
```javascript
{
  success: true,
  code: 200,
  msg: '检查成功',
  data: { exists: boolean } // 是否存在匹配记录
}
```

### 5.15 join - 关联查询
#### 功能描述
实现两表内连接查询，支持条件、排序、字段筛选。
#### 函数签名
```javascript
async join({ joinTable, localField, foreignField, fields = ["*"], query = {}, sort = {} }) => Promise<Object>
```
#### 参数说明
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|--------|------|------|--------|------|
| joinTable | string | 是 | - | 关联表名 |
| localField | string | 是 | - | 主表关联字段 |
| foreignField | string | 是 | - | 关联表关联字段 |
| fields | Array<string> | 否 | ["*"] | 查询字段（可指定表别名，如 `table1.field1`） |
| query | Object | 否 | {} | 查询条件 |
| sort | Object | 否 | {} | 排序条件 |

#### 返回值
```javascript
{
  success: true,
  code: 200,
  msg: '查询成功',
  data: Array // 关联查询结果列表
}
```

### 5.16 deleteMany - 批量删除（ID数组）
#### 功能描述
根据ID数组批量删除记录。
#### 函数签名
```javascript
async deleteMany(ids = []) => Promise<Object>
```
#### 参数说明
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|--------|------|------|--------|------|
| ids | Array<number/string> | 否 | [] | 待删除记录的ID数组 |

#### 返回值
| 场景 | 返回结构 |
|------|----------|
| ID数组为空 | `{ success: false, code: 400, msg: '参数缺失', data: {} }` |
| 删除成功 | `{ success: true, code: 200, msg: '删除成功', data: { affectedRows: number } }` |

### 5.17 softDelete - 软删除
#### 功能描述
设置 `deleted_at` 字段为当前时间，实现逻辑删除（非物理删除）。
#### 函数签名
```javascript
async softDelete(id) => Promise<Object>
```
#### 参数说明
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| id | number/string | 是 | 记录主键ID |

#### 返回值
| 场景 | 返回结构 |
|------|----------|
| 参数为空 | `{ msg: '参数缺失' }` |
| 删除成功 | `{ affectedRows: number }` |

### 5.18 restore - 恢复软删除记录
#### 功能描述
将 `deleted_at` 字段置为 `null`，恢复软删除的记录。
#### 函数签名
```javascript
async restore(id) => Promise<Object>
```
#### 参数说明
| 参数名 | 类型 | 必填 | 说明 |
|--------|------|------|------|
| id | number/string | 是 | 记录主键ID |

#### 返回值
| 场景 | 返回结构 |
|------|----------|
| 参数为空 | `{ msg: '参数缺失' }` |
| 恢复成功 | `{ affectedRows: number }` |

### 5.19 findTrashed - 查询软删除记录
#### 功能描述
查询已被软删除的记录（`deleted_at` 不为空）。
#### 函数签名
```javascript
async findTrashed({ query = {}, fields = [], limit = 20, offset = 0 } = {}) => Promise<Array>
```
#### 参数说明
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|--------|------|------|--------|------|
| query | Object | 否 | {} | 额外查询条件 |
| fields | Array<string> | 否 | [] | 查询字段 |
| limit | number | 否 | 20 | 限制条数 |
| offset | number | 否 | 0 | 偏移量 |

#### 返回值
| 类型 | 说明 |
|------|------|
| Promise<Array> | 软删除记录列表，按 `deleted_at` 降序排列 |

### 5.20 forceDelete - 强制删除软删除记录
#### 功能描述
物理删除指定天数前被软删除的记录，用于清理历史数据。
#### 函数签名
```javascript
async forceDelete(days = 30) => Promise<Object>
```
#### 参数说明
| 参数名 | 类型 | 必填 | 默认值 | 说明 |
|--------|------|------|--------|------|
| days | number | 否 | 30 | 天数（删除N天前的软删除记录） |

#### 返回值
| 类型 | 说明 |
|------|------|
| Promise<Object> | `{ affectedRows: number }` - 受影响的行数 |

### 5.21 stats - 统计总数与今日新增
#### 功能描述
统计表中总记录数和今日新增记录数（按 `created_at` 字段）。
#### 函数签名
```javascript
async stats() => Promise<Object>
```
#### 返回值
| 类型 | 结构 |
|------|------|
| Promise<Object> | `{ total: number, today: number }` - total为总记录数，today为今日新增数 |

## 六、使用示例
### 6.1 基础使用（子类继承）
```javascript
// UserService.js
import Service from './Service.js';

class UserService extends Service {
  constructor() {
    // 指定表名和数据库连接名
    super('user', 'default');
  }

  // 自定义业务方法
  async findByUsername(username) {
    return this.findOne({ query: { username } });
  }
}

export default new UserService();
```

### 6.2 调用示例
```javascript
import userService from './UserService.js';

// 1. 查询用户列表（分页）
const userPage = await userService.query({
  current: 1,
  pageSize: 10,
  query: { status: 1 },
  sort: { create_at: 'desc' },
  field: ['id', 'username', 'email']
});

// 2. 新增用户
const insertRes = await userService.insert({
  username: 'test',
  email: 'test@example.com',
  created_at: '2024-01-01 12:00:00'
});

// 3. 软删除用户
await userService.softDelete(1);

// 4. 恢复软删除用户
await userService.restore(1);

// 5. 关联查询（用户-订单）
const userOrder = await userService.join({
  joinTable: 'order',
  localField: 'id',
  foreignField: 'user_id',
  fields: ['user.username', 'order.order_no'],
  query: { user.status: 1 }
});
```

## 七、注意事项
1. 所有方法均依赖 `Chan` 全局对象，需确保初始化时已配置数据库连接和相关配置项；
2. 日期字段格式化仅处理 `_dateFields` 中的字段，若需扩展可在子类中重写该属性；
3. 事务操作（`updateMany`）需数据库支持事务，否则会抛出异常；
4. 分页查询 `query` 方法会自动限制 `pageSize` 不超过 `limit`，避免大数据量查询；
5. 软删除相关方法依赖 `deleted_at` 字段，需确保表结构中包含该字段。