# 终端设备接口文档

> **基础地址**: `http://{服务器IP}:8580/api`
> **认证方式**: Bearer Token（JWT）
> **数据格式**: JSON
> **字符编码**: UTF-8

---

## 目录

1. [发送短信验证码](#1-发送短信验证码)
2. [手机验证码登录](#2-手机验证码登录)
3. [用户作品列表](#3-用户作品列表)
4. [作品详情](#4-作品详情)
5. [通用错误码](#5-通用错误码)

---

## 接口详情

### 1. 发送短信验证码

向指定手机号发送6位数字验证码，验证码有效期 **5分钟**。

```
POST /device/auth/sms/send
```

**请求头**

| 参数 | 值 |
|------|------|
| Content-Type | application/json |

**请求参数（Body）**

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| phone | string | 是 | 手机号，11位 |

**请求示例**

```json
{
  "phone": "13800138000"
}
```

**响应参数**

| 字段 | 类型 | 说明 |
|------|------|------|
| code | int | 状态码，200=成功 |
| message | string | 描述信息 |
| data | null | 无数据返回 |

**响应示例**

```json
{
  "code": 200,
  "message": "success",
  "data": null,
  "timestamp": "2026-04-11T12:00:00",
  "path": "/api/device/auth/sms/send"
}
```

**注意事项**

- 同一手机号 **60秒内** 只能发送一次
- 同一手机号每天最多发送 **15次**
- 验证码 **5分钟** 内有效，仅可使用一次

---

### 2. 手机验证码登录

使用手机号 + 短信验证码登录，成功返回 JWT Token 和用户信息。

```
POST /device/auth/login/sms
```

**请求头**

| 参数 | 值 |
|------|------|
| Content-Type | application/json |

**请求参数（Body）**

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| phone | string | 是 | 手机号，11位 |
| smsCode | string | 是 | 6位数字验证码 |

**请求示例**

```json
{
  "phone": "13800138000",
  "smsCode": "406436"
}
```

**响应参数**

| 字段 | 类型 | 说明 |
|------|------|------|
| code | int | 状态码，200=成功 |
| message | string | 描述信息 |
| data | object | 登录数据 |
| data.token | string | JWT Token，后续接口需携带 |
| data.userId | long | 用户ID |
| data.username | string | 用户名 |
| data.nickname | string | 昵称 |
| data.avatar | string | 头像URL，可为 null |
| data.phone | string | 手机号（脱敏，如 138****8000） |

**响应示例**

```json
{
  "code": 200,
  "message": "success",
  "data": {
    "token": "eyJhbGciOiJIUzM4NCJ9.eyJ0ZW5hbnRJZCI6OCwidXNlcm5hbWUiOiJkZW1vIiwic3ViIjoiOSIsImlhdCI6MTc3NTg4MjgwNywiZXhwIjoxNzc2NDg3NjA3fQ.xxx",
    "userId": 9,
    "username": "demo",
    "nickname": "小明",
    "avatar": "https://example.com/avatar.png",
    "phone": "137****9302"
  },
  "timestamp": "2026-04-11T12:00:00",
  "path": "/api/device/auth/login/sms"
}
```

**错误情况**

| code | 说明 |
|------|------|
| 404 | 该手机号未注册 |
| 403 | 账号已被禁用 |
| 400 | 验证码错误 |
| 400 | 验证码已过期 |
| 400 | 验证码不能为空 |

---

### 3. 用户作品列表

获取当前登录用户的创作作品列表（分页）。

```
GET /device/works
```

**请求头**

| 参数 | 值 | 必填 | 说明 |
|------|------|------|------|
| Authorization | Bearer {token} | 是 | 登录接口返回的 Token |

**请求参数（Query）**

| 字段 | 类型 | 必填 | 默认值 | 说明 |
|------|------|------|--------|------|
| page | int | 否 | 1 | 页码，从1开始 |
| pageSize | int | 否 | 10 | 每页条数 |
| status | string | 否 | - | 筛选状态：`draft`/`unpublished`/`pending_review`/`published`/`rejected` |
| keyword | string | 否 | - | 标题关键词搜索 |

**请求示例**

```
GET /api/device/works?page=1&pageSize=10&status=unpublished&keyword=测试
```

**响应参数**

| 字段 | 类型 | 说明 |
|------|------|------|
| code | int | 状态码，200=成功 |
| data | object | 分页数据 |
| data.list | array | 作品列表 |
| data.total | long | 总记录数 |
| data.page | long | 当前页码 |
| data.pageSize | long | 每页条数 |

**list 数组项字段**

| 字段 | 类型 | 说明 |
|------|------|------|
| id | long | 作品ID |
| title | string | 作品标题 |
| coverUrl | string | 封面图URL，可为 null |
| description | string | 作品描述，可为 null |
| status | string | 状态：`draft`（草稿）/ `unpublished`（待发布）/ `pending_review`（审核中）/ `published`（已发布）/ `rejected`（已拒绝） |
| authorName | string | 作者名称 |
| leaiStatus | int | AI创作进度：`-1`=失败, `0`=初始化, `1`=排队中, `2`=处理中, `3`=完成, `4`=已编目, `5`=已配音 |
| pageCount | int | 绘本页数，可为 null |
| createTime | string | 创建时间，格式 `yyyy-MM-ddTHH:mm:ss` |
| modifyTime | string | 修改时间，格式 `yyyy-MM-ddTHH:mm:ss` |

**响应示例**

```json
{
  "code": 200,
  "message": "success",
  "data": {
    "list": [
      {
        "id": 38,
        "title": "我的绘本",
        "coverUrl": "https://oss.example.com/cover.png",
        "description": "一个有趣的故事",
        "status": "unpublished",
        "authorName": "小明",
        "leaiStatus": 5,
        "pageCount": 6,
        "createTime": "2026-04-10T17:07:00",
        "modifyTime": "2026-04-10T17:10:53"
      },
      {
        "id": 37,
        "title": "春天的故事",
        "coverUrl": "https://oss.example.com/cover2.png",
        "description": null,
        "status": "published",
        "authorName": "小红",
        "leaiStatus": 5,
        "pageCount": 6,
        "createTime": "2026-04-10T16:53:17",
        "modifyTime": "2026-04-10T16:58:54"
      }
    ],
    "total": 15,
    "page": 1,
    "pageSize": 10
  }
}
```

---

### 4. 作品详情

获取指定作品的完整信息，包含所有页面内容（图片、文字、音频）。

```
GET /device/works/{id}
```

**请求头**

| 参数 | 值 | 必填 | 说明 |
|------|------|------|------|
| Authorization | Bearer {token} | 是 | 登录接口返回的 Token |

**路径参数**

| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| id | long | 是 | 作品ID |

**请求示例**

```
GET /api/device/works/38
```

**响应参数**

| 字段 | 类型 | 说明 |
|------|------|------|
| code | int | 状态码，200=成功 |
| data | object | 作品详情 |
| data.id | long | 作品ID |
| data.title | string | 作品标题 |
| data.authorName | string | 作者名称 |
| data.coverUrl | string | 封面图URL |
| data.description | string | 作品描述，可为 null |
| data.status | string | 状态 |
| data.visibility | string | 可见范围：`public`/`designated`/`internal`/`private` |
| data.createTime | string | 创建时间 |
| data.modifyTime | string | 修改时间 |
| data.pages | array | 页面列表，按页码升序排列 |

**pages 数组项字段**

| 字段 | 类型 | 说明 |
|------|------|------|
| pageNo | int | 页码，从1开始 |
| imageUrl | string | 页面图片URL |
| text | string | 页面文字内容，可为 null |
| audioUrl | string | 页面音频URL，可为 null |

**响应示例**

```json
{
  "code": 200,
  "message": "success",
  "data": {
    "id": 38,
    "title": "我的绘本",
    "authorName": "小明",
    "coverUrl": "https://oss.example.com/cover.png",
    "description": "一个有趣的故事",
    "status": "unpublished",
    "visibility": "private",
    "createTime": "2026-04-10T17:07:00",
    "modifyTime": "2026-04-10T17:10:53",
    "pages": [
      {
        "pageNo": 1,
        "imageUrl": "https://oss.example.com/page_0.png",
        "text": "从前有一座山",
        "audioUrl": "https://oss.example.com/page_0.mp3"
      },
      {
        "pageNo": 2,
        "imageUrl": "https://oss.example.com/page_1.png",
        "text": "山里有一座庙",
        "audioUrl": "https://oss.example.com/page_1.mp3"
      },
      {
        "pageNo": 3,
        "imageUrl": "https://oss.example.com/page_2.png",
        "text": "庙里有一个老和尚",
        "audioUrl": null
      }
    ]
  }
}
```

**错误情况**

| code | 说明 |
|------|------|
| 404 | 作品不存在 |
| 403 | 无权访问该作品（非本人作品） |

---

## 5. 通用错误码

所有接口统一响应格式：

```json
{
  "code": 401,
  "message": "未登录或 Token 已过期",
  "timestamp": "2026-04-11T12:00:00",
  "path": "/api/device/works"
}
```

| code | 说明 |
|------|------|
| 200 | 成功 |
| 400 | 请求参数错误 |
| 401 | 未登录或 Token 已过期 |
| 403 | 无权限访问 |
| 404 | 资源不存在 |
| 429 | 请求过于频繁（触发限流） |
| 500 | 服务器内部错误 |

---

## 调用流程

```
┌─────────────────────────────────────────────────────────┐
│                      终端设备调用流程                      │
├─────────────────────────────────────────────────────────┤
│                                                         │
│  1. 发送验证码  POST /device/auth/sms/send              │
│     └─ 参数: { phone }                                  │
│                                                         │
│  2. 验证码登录  POST /device/auth/login/sms             │
│     └─ 参数: { phone, smsCode }                         │
│     └─ 返回: token ← 保存到本地                         │
│                                                         │
│  3. 作品列表    GET  /device/works                       │
│     └─ Header: Authorization: Bearer {token}            │
│     └─ 参数: page, pageSize, status(可选), keyword(可选) │
│                                                         │
│  4. 作品详情    GET  /device/works/{id}                  │
│     └─ Header: Authorization: Bearer {token}            │
│     └─ 返回: 完整页面数据（图片+文字+音频）              │
│                                                         │
└─────────────────────────────────────────────────────────┘
```

**Token 有效期**: 7天（604800秒），过期后需重新登录获取。

**建议**:
- Token 存储在设备本地，每次启动时检查是否过期
- 过期后重新走 发送验证码 → 登录 流程
- 音频和图片资源使用返回的 URL 直接访问 OSS，无需经过本服务
