# API Documentation

Base URL:

```text
http://<host>:<port>
```

Response content type:

```text
application/json; charset=utf-8
```

## Authorization

所有 API 都要求固定的 `Authorization` 请求头。

配置文件：

```yaml
auth:
  secret: "your-fixed-authorization-secret"
```

请求示例：

```http
Authorization: your-fixed-authorization-secret
```

说明：

- 服务端会把 `Authorization` 头的原始值与 `auth.secret` 做精确匹配
- 如果你想使用 `Bearer xxx` 风格，也可以直接把 `auth.secret` 配成完整的 `Bearer xxx`

未授权响应：

```json
{
  "error": "unauthorized"
}
```

## Logging

服务端会输出两级日志：

- `WARNING`
- `ERROR`

当前会重点记录：

- 未授权访问
- 参数错误
- 数据库相关失败
- 图片查找失败

当图片不存在时，日志里会带上尝试过的完整路径，便于排查 Windows 部署目录问题。

## 1. Health Check

`GET /healthz`

用途：

- 检查 API 进程是否存活
- 检查数据库连接是否可用

成功响应：

```json
{
  "ok": true
}
```

## 2. Query Records

`GET /api/v1/records`

### 2.1 增量同步模式

不传 `id_start` / `id_end` 时，接口按当前游标做增量读取：

- 条件：`ID > cursor`
- 排序：`ORDER BY ID ASC`
- 默认 `limit=100`
- 默认 `update_cursor=true`
- 如果查到数据且 `update_cursor=true`，游标推进到本次结果最后一条记录的 `ID`
- 返回结果会自动补上主表里的 `KhID`，并额外附带客户表 `dbo.B_Khzl` 的客户信息
- 返回结果会根据客户表 `KHLbID` 自动关联地区表 `dbo.B_Khlb`，并平铺返回 `area`
- 返回结果也会根据订单表 `HpID` 自动关联布料表 `dbo.B_Hpzl`，并平铺返回 `HpName`

参数：

- `update_cursor`: 可选，布尔值，默认 `true`
- `limit`: 可选，正整数，默认 `100`

示例：

```http
GET /api/v1/records
Authorization: your-fixed-authorization-secret
```

```http
GET /api/v1/records?update_cursor=false&limit=20
Authorization: your-fixed-authorization-secret
```

成功响应示例：

```json
{
  "mode": "incremental",
  "cursor_before": 1200,
  "cursor_after": 1250,
  "origin_cursor": 1000,
  "last_record_id": 1250,
  "updated": true,
  "count": 50,
  "records": [
    {
      "ID": 1201,
      "KhID": 88,
      "BianHaoID": "...",
      "area": "华东地区",
      "HpName": "某某布料",
      "customer": {
        "KhID": 88,
        "KhName": "某某客户"
      }
    }
  ]
}
```

字段说明：

- `mode`: 固定为 `incremental`
- `cursor_before`: 本次请求开始前的游标
- `cursor_after`: 本次请求完成后的游标。如果 `update_cursor=false`，这里会返回本次结果最后一条记录的 ID，但不会实际写入状态文件
- `origin_cursor`: 最近一次手工修改游标前的值；自动增量同步不会改它
- `last_record_id`: 本次返回结果中的最后一条记录 ID；无数据时不存在
- `updated`: 本次是否实际更新了游标状态
- `count`: 返回记录数
- `records[].KhID`: 主表记录中的客户 ID
- `records[].customer`: 客户表补充信息，当前默认包含 `KhID` 和 `KhName`
- `records[].area`: 客户地区，来自 `dbo.B_Khlb` 的 `MingCheng`
- `records[].HpName`: 布料表 `dbo.B_Hpzl` 中关联到的布料名称

### 2.2 范围查询模式

传了 `id_start` 和 `id_end` 时，接口进入范围查询模式：

- 条件：`ID >= id_start AND ID <= id_end`
- 排序：`ORDER BY ID ASC`
- 不会更新游标
- 必须显式传 `update_cursor=false`

参数：

- `id_start`: 必填，非负整数
- `id_end`: 必填，非负整数
- `update_cursor`: 必填，且必须为 `false`
- `limit`: 可选，正整数，默认 `100`

示例：

```http
GET /api/v1/records?id_start=2000&id_end=2100&update_cursor=false
Authorization: your-fixed-authorization-secret
```

成功响应示例：

```json
{
  "mode": "range",
  "cursor_before": 1250,
  "cursor_after": 1250,
  "origin_cursor": 1000,
  "last_record_id": 2050,
  "updated": false,
  "count": 51,
  "id_start": 2000,
  "id_end": 2100,
  "records": [
    {
      "ID": 2000,
      "BianHaoID": "..."
    }
  ]
}
```

### 2.3 参数校验规则

- `limit` 必须大于 0
- `id_start` 和 `id_end` 必须同时出现
- `id_start` 和 `id_end` 必须是非负整数
- `id_start` 不能大于 `id_end`
- 范围查询模式必须显式传 `update_cursor=false`

## 3. Query Current Order Snapshot

`GET /api/v1/records/by-order`

用途：

- 按 `external_order_id` 精确查询当前整单快照
- 返回该 `BianHaoID` 下当前全部 records
- 不读取 cursor，也不推进 cursor
- 返回字段会继续附带现有的 `customer`、`area`、`HpName`

参数：

- `external_order_id`: 必填，字符串，精确对应主表 `D_KhDhd.BianHaoID`

示例：

```http
GET /api/v1/records/by-order?external_order_id=KD20432358
Authorization: your-fixed-authorization-secret
```

成功响应示例：

```json
{
  "mode": "snapshot",
  "external_order_id": "KD20432358",
  "status": "active",
  "snapshot_at": "2026-04-16T10:12:34Z",
  "total_count": 3,
  "records": [
    {
      "ID": 1000001,
      "BianHaoID": "KD20432358",
      "KhID": "KH00999",
      "HpName": "120克本白四面弹单定",
      "area": "周边",
      "customer": {
        "KhID": "KH00999",
        "KhName": "鸿烨服饰"
      }
    }
  ]
}
```

字段说明：

- `mode`: 固定为 `snapshot`
- `external_order_id`: 当前查询的订单号
- `status`: 当前快照状态；查询命中时固定为 `active`
- `snapshot_at`: 服务端生成本次快照响应的时间
- `total_count`: 当前订单快照下 records 数量
- `records`: 当前订单下的全部 records，字段宽度与现有 `GET /api/v1/records` 保持一致

未命中响应示例：

```json
{
  "external_order_id": "KD20432358",
  "status": "not_found",
  "message": "未找到该 external_order_id 对应的订单"
}
```

## 4. Query Customer Orders

`GET /api/v1/records/by-customer`

用途：

- 按客户名精确查询该客户名下全部订单
- 底层先查询 `D_KhDhd` 明细，再按 `BianHaoID` 聚合为订单主体
- 每个订单主体下的明细结构与现有 `GET /api/v1/records/by-order` 完全一致
- 不读取 cursor，也不推进 cursor
- 结果按配置项 `customer_orders.sort_column` 倒序排列，默认 `KdRiQi`

参数：

- `customer_name_b64`: 必填，客户名的 Base64URL 编码；精确匹配客户表 `dbo.B_Khzl.KhName`

示例：

```http
GET /api/v1/records/by-customer?customer_name_b64=6bi_54OI5pyN6aWw
Authorization: your-fixed-authorization-secret
```

成功响应示例：

```json
{
  "mode": "customer_orders",
  "customer_name": "鸿烨服饰",
  "status": "active",
  "snapshot_at": "2026-05-09T10:12:34Z",
  "total_orders": 2,
  "orders": [
    {
      "mode": "snapshot",
      "external_order_id": "KD20432358",
      "status": "active",
      "snapshot_at": "2026-05-09T10:12:34Z",
      "total_count": 2,
      "records": [
        {
          "ID": 1000001,
          "BianHaoID": "KD20432358",
          "KhID": "KH00999",
          "HpName": "120克本白四面弹单定",
          "area": "周边",
          "customer": {
            "KhID": "KH00999",
            "KhName": "鸿烨服饰"
          }
        }
      ]
    }
  ]
}
```

字段说明：

- `mode`: 固定为 `customer_orders`
- `customer_name`: 当前查询的客户名
- `status`: 查询命中时固定为 `active`
- `snapshot_at`: 服务端生成本次响应的时间
- `total_orders`: 当前客户名下聚合后的订单数
- `orders`: 订单列表；每个订单元素的结构与 `GET /api/v1/records/by-order` 返回结构保持一致

未命中响应示例：

```json
{
  "customer_name": "鸿烨服饰",
  "status": "not_found",
  "message": "未找到该 customer_name 对应的订单"
}
```

配置：

```yaml
customer_orders:
  sort_column: "KdRiQi"
```

- `sort_column` 用于控制订单聚合结果的排序字段，默认值为 `KdRiQi`

## 5. Query Customer List

`GET /api/v1/customers`

用途：

- 按客户主档表 `dbo.B_Khzl` 拉取客户列表
- 每次固定返回 `10` 个客户
- 只返回 `KhID` 和 `KhName`
- 用于外部系统按客户维度做同步
- 使用游标参数 `cursor_id` 续拉，不使用 `page` / `pageSize`

排序与游标规则：

- 底层按 `ZZRQ ASC, KhID ASC` 排序
- `ZZRQ` 越早的客户越先返回，避免从新到旧翻页时遗漏新数据
- `cursor_id` 传上一页最后一条客户的 `KhID`
- 服务端会先查出该 `KhID` 对应的 `ZZRQ`，再从 `(ZZRQ, KhID)` 这个排序位置之后继续取下一批 `10` 条
- 如果多个客户 `ZZRQ` 相同，会继续用 `KhID` 做稳定次序

参数：

- `cursor_id`: 可选，上一页最后一条客户的 `KhID`

示例：

```http
GET /api/v1/customers
Authorization: your-fixed-authorization-secret
```

```http
GET /api/v1/customers?cursor_id=KH01485
Authorization: your-fixed-authorization-secret
```

成功响应示例：

```json
{
  "mode": "customers",
  "cursor_id": "KH01485",
  "next_cursor_id": "KH01495",
  "snapshot_at": "2026-05-14T15:00:00Z",
  "count": 10,
  "customers": [
    {
      "customer_id": "KH01486",
      "customer_name": "A亿佳服饰"
    }
  ]
}
```

字段说明：

- `mode`: 固定为 `customers`
- `cursor_id`: 本次请求使用的游标；首批为空
- `next_cursor_id`: 本页最后一条客户的 `KhID`；下一页继续传它即可
- `snapshot_at`: 服务端生成本次响应的时间
- `count`: 当前返回客户数，最大固定为 `10`
- `customers[].customer_id`: 客户 ID，对应 `dbo.B_Khzl.KhID`
- `customers[].customer_name`: 客户名称，对应 `dbo.B_Khzl.KhName`

未命中游标响应示例：

```json
{
  "cursor_id": "KH99999",
  "status": "not_found",
  "message": "未找到该 cursor_id 对应的客户"
}
```

## 6. Query I_Sale Records By BianHaoID

`GET /api/v1/i-sale/by-bianhao`

用途：

- 按 `BianHaoID` 查询 `dbo.I_Sale` 中的业务明细记录
- 为收款和退款记录补充对应业务单据的信息
- 返回 `I_Sale` 的全量字段
- 不读取 cursor，也不推进 cursor

注意：

- 同一个 `BianHaoID` 在 `I_Sale` 中可能命中多条明细行
- 因此该接口返回的是 `records[]`，不是单对象详情

参数：

- `bianhao_id`: 必填，精确匹配 `dbo.I_Sale.BianHaoID`

示例：

```http
GET /api/v1/i-sale/by-bianhao?bianhao_id=XT20203911
Authorization: your-fixed-authorization-secret
```

成功响应示例：

```json
{
  "mode": "i_sale_records",
  "bianhao_id": "XT20203911",
  "status": "active",
  "snapshot_at": "2026-05-14T15:10:00Z",
  "total_count": 1,
  "records": [
    {
      "BianHaoID": "XT20203911",
      "DanType": "客户退货单",
      "KhID": "KH01204"
    }
  ]
}
```

字段说明：

- `mode`: 固定为 `i_sale_records`
- `bianhao_id`: 当前查询的业务单号
- `status`: 查询命中时固定为 `active`
- `snapshot_at`: 服务端生成本次响应的时间
- `total_count`: 当前 `BianHaoID` 命中的 `I_Sale` 明细行数
- `records`: `I_Sale` 全量字段明细列表

未命中响应示例：

```json
{
  "bianhao_id": "XT404",
  "status": "not_found",
  "message": "未找到该 bianhao_id 对应的 I_Sale 记录"
}
```

## 7. Query I_Sale Records By Customer

`GET /api/v1/i-sale/by-customer`

用途：

- 按客户 ID 查询 `dbo.I_Sale` 中的业务明细记录
- 用于补足按客户统计时对 `I_Sale` 的直接拉取能力
- 不分页、不使用游标；一次返回当前客户在指定分类下的全部命中记录
- 返回字段不是 `I_Sale` 全 119 列，而是“核心标识 + 全部金额字段 + 全部数量字段 + 备注字段”

当前分类策略：

- `sale`：只取 `DanType = '成品销售单' AND BianHaoID LIKE 'XS%'`
- `sale_return`：只取 `DanType = '客户退货单' AND BianHaoID LIKE 'XT%'`

当前实现决策：

- 用户明确接受低频调用，因此本接口不做分页和游标
- 但为了避免把 `I_Sale` 全 119 列直接暴露给同步方，当前只返回与统计/业务核对最相关的字段组
- 该字段组已结合项目内 `I_SALE_FIELD_ANALYSIS.md` 与线上 `db/columns` 验证

参数：

- `customer_id`: 必填，精确匹配 `dbo.I_Sale.KhID`
- `category`: 必填，当前只支持 `sale`、`sale_return`

示例：

```http
GET /api/v1/i-sale/by-customer?customer_id=KH00308&category=sale
Authorization: your-fixed-authorization-secret
```

```http
GET /api/v1/i-sale/by-customer?customer_id=KH00308&category=sale_return
Authorization: your-fixed-authorization-secret
```

成功响应示例：

```json
{
  "mode": "i_sale_customer_records",
  "customer_id": "KH00308",
  "category": "sale",
  "status": "active",
  "snapshot_at": "2026-05-14T20:00:00Z",
  "total_count": 2,
  "records": [
    {
      "SubID": 1,
      "BianHaoID": "XS20367431",
      "DanType": "成品销售单",
      "KhID": "KH00308",
      "RiQi": "2026-05-14T00:00:00Z",
      "KdRiQi": "2026-05-14T19:08:23Z",
      "JieSunFS": "欠款",
      "HpID": "HP00001",
      "JianShu": "1.00",
      "ShuLiang": "20.00",
      "ShuLiangT": "20.00",
      "DanJia": "32.000000",
      "JinE": "640.000000",
      "DingJin": "0.00",
      "YfJinE": "0.00",
      "SfJinE": "0.00",
      "ZkJinE": "0.00",
      "LjQK": "0.00",
      "BeiZhu": "",
      "BeiZhuC": "",
      "BeiZhuD": "",
      "MeoD": "普通单据"
    }
  ]
}
```

字段说明：

- `mode`: 固定为 `i_sale_customer_records`
- `customer_id`: 当前查询的客户 ID
- `category`: 当前查询的分类
- `status`: 查询命中时固定为 `active`
- `snapshot_at`: 服务端生成本次响应的时间
- `total_count`: 当前客户在该分类下命中的 `I_Sale` 明细行数
- `records`: 当前字段集包含以下几类字段

核心标识字段：

- `SubID`
- `BianHaoID`
- `DanType`
- `KhID`
- `RiQi`
- `KdRiQi`
- `JieSunFS`
- `HpID`
- `JiJiaDW`

数量相关字段：

- `JianShu`
- `ShuLiang`
- `ShuLiangZ`
- `ShuLiangK`
- `ShuLiangS`
- `JianShuT`
- `ShuLiangT`
- `ShuLiangZT`
- `ShuLiangKT`
- `ShuLiangST`
- `Sl1` ~ `Sl10`

金额相关字段：

- `DingJin`
- `YfJinE`
- `SfJinE`
- `ZkJinE`
- `LjQK`
- `DanJia`
- `JinE`
- `DanJiaT`
- `JinET`
- `DanJiaCB`
- `JinECB`
- `JinE_SL`

备注相关字段：

- `BeiZhu`
- `BeiZhuC`
- `BeiZhuD`
- `MeoA`
- `MeoB`
- `MeoC`
- `MeoD`

未命中响应示例：

```json
{
  "customer_id": "KH00308",
  "category": "sale",
  "status": "not_found",
  "message": "未找到该 customer_id 和 category 对应的 I_Sale 记录"
}
```

参数错误：

- `customer_id` 为空时返回 `400`
- `category` 非 `sale` / `sale_return` 时返回 `400`

## 7. Query Customer Finance Records

`GET /api/v1/finance/by-customer`

用途：

- 按客户名精确查询该客户名下的财务记录
- 当前销售/销退只认 `dbo.I_Sale`
- 当前收款/退款只认 `dbo.F_Skd`
- 支持按记录类型筛选，并支持控制是否包含真实资金变动、是否包含挂账/欠款调整
- 不读取 cursor，也不推进 cursor

参数：

- `customer_name_b64`: 必填，客户名的 Base64URL 编码；精确匹配客户表 `dbo.B_Khzl.KhName`
- `record_types`: 可选，逗号分隔；支持 `sale`、`sale_return`、`receipt`、`refund`；不传默认全部
- `include_cash_movement`: 可选，布尔值；默认 `true`
- `include_adjustments`: 可选，布尔值；默认 `false`
- `filter_sk`: 可选，布尔值；默认 `false`；当前 `receipt` 口径下保留该参数但不生效

当前口径：

- `sale`: `I_Sale.DanType = '成品销售单'`
- `sale_return`: `I_Sale.DanType = '客户退货单'`
- `receipt`: 仅纳入 `F_Skd.BianHaoID LIKE 'SK%'` 的收款流水，不纳入 `XS%` 记录
- `refund`: `F_Skd.sType = 1 AND F_Skd.BianHaoID LIKE 'XT%'`
- `receipt` 的返回金额字段仍保留 `F_Skd.FkJinE`、`F_Skd.ZkJinE` 等原始值，但当前口径不再使用 `FkJinE` 作为筛选条件
- `refund` 的统计和筛选金额字段为 `F_Skd.YfJinE`
- `include_cash_movement` 与 `include_adjustments` 当前仅影响 `refund`；`receipt` 统一按 `SK%` 全量统计
- `include_cash_movement=true` 时，`refund` 取 `YfJinE < 0`
- `include_adjustments=true` 时，`refund` 额外包含 `YfJinE = 0` 的挂账/欠款调整记录

当前已验证的 `sType` 规则：

- `sType = 1`：当前财务 API 使用的业务单据关联流水，样本中覆盖 `XS...` 与 `XT...`
- `sType = 0`：当前样本对应 `SK...` 型独立收款流水；当前 `receipt` 查询会纳入它，`refund` 不会

示例：

```http
GET /api/v1/finance/by-customer?customer_name_b64=6bi_54OI5pyN6aWw&record_types=sale,receipt,refund&include_cash_movement=true&include_adjustments=false
Authorization: your-fixed-authorization-secret
```

成功响应示例：

```json
{
  "mode": "customer_finance",
  "customer_name": "鸿烨服饰",
  "customer_ids": [
    "KH00999"
  ],
  "record_types": [
    "sale",
    "receipt",
    "refund"
  ],
  "include_cash_movement": true,
  "include_adjustments": false,
  "status": "active",
  "snapshot_at": "2026-05-10T21:30:00Z",
  "total_count": 3,
  "sales_count": 1,
  "sale_returns_count": 0,
  "receipts_count": 1,
  "refunds_count": 1,
  "sales": [
    {
      "BianHaoID": "XS20366749",
      "DanType": "成品销售单",
      "KhID": "KH00999"
    }
  ],
  "sale_returns": [],
  "receipts": [
    {
      "BianHaoID": "XS20215718",
      "FkJinE": "378.00",
      "JieSunFS": "微信"
    }
  ],
  "refunds": [
    {
      "BianHaoID": "XT20200743",
      "YfJinE": "-2201.00",
      "FkJinE": "0.00",
      "JieSunFS": "微信"
    }
  ]
}
```

未命中响应示例：

```json
{
  "customer_name": "鸿烨服饰",
  "status": "not_found",
  "message": "未找到该 customer_name 对应的财务记录"
}
```

参数错误示例：

```json
{
  "error": "record_types contains unsupported value"
}
```

## 6. Database Inspect Permission Check

`GET /api/v1/db/permissions`

用途：

- 检查当前数据库账号是否具备数据库分析所需的基础能力
- 尝试验证数据库连接、获取当前库名、列出可见表、读取字段元数据、读取 `sys` 元数据、检查 `VIEW DEFINITION`、以及对所有可见表执行 `SELECT TOP (1)`
- 返回总体是否可用，以及各项检查结果

配置开关：

```yaml
db_inspect:
  enabled: true
```

成功响应示例：

```json
{
  "can_inspect": true,
  "database_name": "RCYH2020",
  "visible_table_count": 32,
  "checks": [
    {
      "name": "database_connection",
      "ok": true
    },
    {
      "name": "list_base_tables",
      "ok": true
    },
    {
      "name": "read_all_visible_table_columns",
      "ok": true
    },
    {
      "name": "select_top_1_from_all_visible_tables",
      "ok": true
    }
  ]
}
```

字段说明：

- `can_inspect`: 是否满足数据库分析所需的整体权限和可读能力
- `database_name`: 当前连接到的数据库名
- `visible_table_count`: 当前账号可见的基础表数量
- `checks`: 分项检查结果
- `failed_column_tables`: 无法读取字段元数据的表清单，仅检查失败时出现
- `failed_select_tables`: 无法执行 `SELECT TOP (1)` 的表清单，仅检查失败时出现

## 7. List Database Tables

`GET /api/v1/db/tables`

用途：

- 返回当前数据库中当前账号可见的所有基础表

成功响应示例：

```json
{
  "count": 2,
  "tables": [
    {
      "schema": "dbo",
      "name": "D_KhDhd",
      "full_name": "dbo.D_KhDhd"
    },
    {
      "schema": "dbo",
      "name": "B_Khzl",
      "full_name": "dbo.B_Khzl"
    }
  ]
}
```

## 8. List Table Columns

`GET /api/v1/db/columns`

参数：

- `table`: 必填，表名，格式为 `schema.table` 或 `db.schema.table`

用途：

- 返回指定表的字段列表
- 返回字段名、类型、是否可空、长度、精度、顺序等信息

示例：

```http
GET /api/v1/db/columns?table=dbo.D_KhDhd
Authorization: your-fixed-authorization-secret
```

成功响应示例：

```json
{
  "table": "dbo.D_KhDhd",
  "count": 2,
  "columns": [
    {
      "name": "ID",
      "data_type": "int",
      "nullable": false,
      "ordinal_position": 1
    },
    {
      "name": "BianHaoID",
      "data_type": "nvarchar",
      "nullable": true,
      "max_length": 50,
      "ordinal_position": 2
    }
  ]
}
```

## 9. Generic Table Select

`GET /api/v1/db/select`

参数：

- `table`: 必填，表名，格式为 `schema.table` 或 `db.schema.table`
- `columns`: 必填，逗号分隔的字段名列表，例如 `ID,BianHaoID`
- `where`: 可选，受限条件表达式；支持 `=` `!=` `<>` `>` `>=` `<` `<=` `LIKE` `IS NULL` `IS NOT NULL`，多个条件只支持用 `AND` 连接；字符串字面量必须写成单引号，例如 `DanType = '客户退货单'`
- `limit`: 可选，正整数；不传时使用默认值，并受服务端上限限制

用途：

- 对指定表执行通用只读查询
- 仅返回请求中指定的字段集合
- 当前实现固定为 `SELECT TOP (limit)`，支持受限 `WHERE`，不支持自定义 SQL

示例：

```http
GET /api/v1/db/select?table=dbo.D_KhDhd&columns=ID,BianHaoID&limit=10
Authorization: your-fixed-authorization-secret
```

```http
GET /api/v1/db/select?table=dbo.I_Sale&columns=BianHaoID,DanType,ShuLiang&where=DanType%20%3D%20%27%E5%AE%A2%E6%88%B7%E9%80%80%E8%B4%A7%E5%8D%95%27%20AND%20ShuLiang%20%3C%200&limit=10
Authorization: your-fixed-authorization-secret
```

成功响应示例：

```json
{
  "table": "dbo.D_KhDhd",
  "columns": [
    "ID",
    "BianHaoID"
  ],
  "count": 2,
  "rows": [
    {
      "ID": 1001,
      "BianHaoID": "KD20432358"
    },
    {
      "ID": 1002,
      "BianHaoID": "KD20432359"
    }
  ]
}
```

参数错误示例：

```json
{
  "error": "where is invalid"
}
```

当配置关闭时：

```json
{
  "error": "db inspect api is disabled"
}
```

## 10. Get Current Cursor

`GET /api/v1/cursor`

用途：

- 查看当前游标值
- 查看最近一次人工修改游标前的 `origin_cursor`

示例：

```http
GET /api/v1/cursor
Authorization: your-fixed-authorization-secret
```

成功响应示例：

```json
{
  "cursor": 1500,
  "origin_cursor": 1250
}
```

## 11. Set Cursor

`POST /api/v1/cursor/set`

用途：

- 人工把游标改到指定值
- 每次手工修改时，都会把修改前的游标保存到 `origin_cursor`

请求体：

```json
{
  "value": 1500
}
```

规则：

- `value` 必须是非负整数
- 调用成功后：
  - `cursor` 更新为 `value`
  - `origin_cursor` 保存修改前的 `cursor`

请求示例：

```http
POST /api/v1/cursor/set
Authorization: your-fixed-authorization-secret
Content-Type: application/json

{
  "value": 1500
}
```

成功响应示例：

```json
{
  "cursor_before": 1250,
  "cursor_after": 1500,
  "origin_cursor": 1250,
  "updated": true
}
```

## 12. Get Image

`GET /api/v1/image`

用途：

- 根据给定文件名，从配置的图片目录读取对应文件
- 找到后返回图片内容的 Base64 字符串
- 找不到时返回 `404`

配置文件：

```yaml
image:
  directory: "./images"
```

参数：

- `name`: 可选，原始文件名，必须是纯文件名，不能包含路径
- `name_b64`: 可选，文件名的 Base64URL 编码；当文件名包含 `#` 等 URL 特殊字符时推荐使用
- 如果 `name` 不带后缀，服务端会按 `.jpg`、`.png` 的顺序自动尝试

说明：

- `name` 和 `name_b64` 二选一即可
- 如果文件名里包含 `#`，很多客户端会在真正发请求前把它后面的内容当作 URL fragment 截掉，因此推荐使用 `name_b64`

示例：

```http
GET /api/v1/image?name=test
Authorization: your-fixed-authorization-secret
```

```http
GET /api/v1/image?name_b64=YWJjIzEyMw
Authorization: your-fixed-authorization-secret
```

上面的 `name_b64=YWJjIzEyMw` 对应原始文件名：

```text
abc#123
```

成功响应示例：

```json
{
  "name": "test.jpg",
  "content_type": "image/jpeg",
  "data": "/9j/4AAQSkZJRgABAQ..."
}
```

错误响应示例：

```json
{
  "error": "image not found"
}
```

说明：

- `data` 是图片二进制内容的 Base64 编码
- `content_type` 会优先根据文件扩展名推断，推断不出来时会回退到内容检测
- 如果 `name` 没有后缀，会优先查找 `name.jpg`，找不到再查 `name.png`
- 如果文件名中含有 `#`、空格等特殊字符，优先使用 `name_b64`
- 为了安全，`name` 不能带目录分隔符，也不能包含 `..`

## 13. Cursor State File

默认文件：

```text
./data/cursor-state.json
```

文件内容示例：

```json
{
  "cursor": 1500,
  "origin_cursor": 1250
}
```

说明：

- `cursor`: 当前增量同步游标
- `origin_cursor`: 最近一次人工修改游标前的值
