main: 增强会话功能,支持归档与消息检索

- 添加会话归档接口及相关服务逻辑,并确保幂等性
- 实现单条消息获取接口,校验消息所属会话
- 增加 SSE 增量推送与实时消息订阅功能
- 提供相关的测试用例覆盖新功能
- 更新接口文档,完善 OpenAPI 规范,新增多项示例
This commit is contained in:
2025-12-14 21:58:05 +08:00
parent 6356baacc0
commit 318571a6d9
7 changed files with 531 additions and 47 deletions

View File

@@ -18,65 +18,122 @@
## 接口
### 创建会话
- `POST /sessions`
- 请求体字段
- `session_name` (string, 可选,<=255):会话名称。
- 响应 201 字段:
- `session_id` (uuid)
- `session_name` (string|null)
- `status` (`OPEN|LOCKED|CLOSED`)
- `last_seq` (int)
- `last_message_id` (uuid|null)
- `created_at` / `updated_at`
- 请求体字段
| 字段 | 必填 | 类型 | 说明 |
| --- | --- | --- | --- |
| session_name | 否 | string(≤255) | 会话名称 |
- 响应 201JSON
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| session_id | uuid | 主键 |
| session_name | string|null | 会话名 |
| status | enum | `OPEN|LOCKED|CLOSED` |
| last_seq | int | 当前最大 seq |
| last_message_id | uuid|null | 最后一条消息 |
| created_at, updated_at | datetime | 时间戳 |
- 错误401 未授权
### 追加消息
- `POST /sessions/{session_id}/messages`
- 请求体字段
- `role` (required, `USER|AGENT|TOOL|SYSTEM`)
- `type` (required, string, <=64),如 `user.prompt`/`agent.message` 等。
- `content` (string|null)
- `payload` (object|null) 作为 jsonb 存储。
- `reply_to` (uuid|null)
- `dedupe_key` (string|null, <=128) 幂等键。
- 响应 201 字段:
- `message_id` (uuid)
- `session_id` (uuid)
- `seq` (int会话内递增)
- `role` / `type` / `content` / `payload` / `reply_to` / `dedupe_key`
- `created_at`
- 403违反状态门禁CLOSED 禁止LOCKED 禁止 user.prompt
- 幂等:同 session + dedupe_key 返回已有消息(同 `message_id/seq`)。
- 请求体字段
| 字段 | 必填 | 类型 | 说明 |
| --- | --- | --- | --- |
| role | 是 | enum | `USER|AGENT|TOOL|SYSTEM` |
| type | 是 | string(≤64) | 如 `user.prompt`/`agent.message` 等 |
| content | 否 | string | 文本内容 |
| payload | 否 | object | jsonb 结构 |
| reply_to | 否 | uuid | 引用消息 |
| dedupe_key | 否 | string(≤128) | 幂等键 |
- 响应 201JSON
字段:`message_id, session_id, seq, role, type, content, payload, reply_to, dedupe_key, created_at`
- 幂等:同 session + dedupe_key 返回已存在的消息(同 `message_id/seq`)。
- 错误401 未授权403 违反状态门禁CLOSED 禁止LOCKED 禁止 user.prompt404 session 不存在422 校验失败。
### 按序增量查询
- `GET /sessions/{session_id}/messages?after_seq=0&limit=50`
- 查询参数
- `after_seq` (int, 默认 0):仅返回大于该 seq 的消息。
- `limit` (int, 默认 50<=200)。
- 查询参数
| 参数 | 默认 | 类型 | 说明 |
| --- | --- | --- | --- |
| after_seq | 0 | int | 仅返回 seq 大于该值 |
| limit | 50 | int(≤200) | 返回数量上限 |
- 响应 200`data` 数组,元素字段同“追加消息”响应。
- 错误401/404/422
### 会话列表
- `GET /sessions?page=1&per_page=15&status=OPEN&q=keyword`
- 查询参数
- `page` (int, 默认 1)
- `per_page` (int, 默认 15<=100)
- `status` (`OPEN|LOCKED|CLOSED`,可选)
- `q` (string可选`session_name` ILIKE 模糊匹配)
- 响应 200分页结构`data/links/meta``data` 每项字段:
- `session_id, session_name, status, last_seq, created_at, updated_at`
- `last_message_id`
- `last_message_at`
- `last_message_preview`content 截断 120content 为空则空字符串)
- `last_message_role, last_message_type`
- 排序:`updated_at` DESC
- 查询参数
| 参数 | 默认 | 类型 | 说明 |
| --- | --- | --- | --- |
| page | 1 | int | 分页页码 |
| per_page | 15 | int(≤100) | 分页大小 |
| status | - | enum | 过滤 `OPEN|LOCKED|CLOSED` |
| q | - | string | ILIKE 模糊匹配 session_name |
- 响应 200分页结构`data/links/meta``data` 每项字段:
| 字段 | 类型 | 说明 |
| --- | --- | --- |
| session_id | uuid | 会话主键 |
| session_name | string|null | 名称 |
| status | enum | `OPEN|LOCKED|CLOSED` |
| last_seq | int | 当前最大 seq |
| last_message_id | uuid|null | 最后一条消息 |
| last_message_at | datetime|null | 最后一条消息时间 |
| last_message_preview | string | content 截断 120空内容返回空字符串 |
| last_message_role | string|null | 最后消息角色 |
| last_message_type | string|null | 最后消息类型 |
| created_at, updated_at | datetime | 时间戳 |
- 排序:`updated_at` DESC
- 错误401/422
### 会话更新
- `PATCH /sessions/{session_id}`
- 请求体(至少提供一项,否则 422
- `session_name` (string, 1..255,可选,自动 trim)
- `status` (`OPEN|LOCKED|CLOSED`,可选)
- 请求体(至少一项,否则 422
| 字段 | 必填 | 类型 | 说明 |
| --- | --- | --- | --- |
| session_name | 否 | string 1..255 | 自动 trim |
| status | 否 | enum | `OPEN|LOCKED|CLOSED` |
- 规则:
- `CLOSED` 不可改回 `OPEN`(返回 403
- 任意更新都会刷新 `updated_at`
- 响应 200 字段同会话列表项字段
- 响应 200字段同会话列表项。
- 错误401 未授权403 状态门禁404 session 不存在422 校验失败。
### 获取会话详情
- `GET /sessions/{session_id}`
- 响应 200字段同“会话列表”项。
- 错误401 未授权404 session 不存在。
### 归档会话Archive
- `POST /sessions/{session_id}/archive`
- 行为:将 `status` 置为 `CLOSED`,更新 `updated_at`,幂等(重复归档返回当前状态)。
- 响应 200字段同“会话列表”项status=CLOSED
- 错误401 未授权404 session 不存在。
### 获取单条消息(带会话校验)
- `GET /sessions/{session_id}/messages/{message_id}`
- 行为:校验 `message.session_id` 与路径参数一致,否则 404。
- 响应 200字段同“追加消息”响应。
- 错误401 未授权404 不存在或不属于该会话。
### SSE 实时增量
- `GET /sessions/{session_id}/sse?after_seq=123`
- 头部:`Accept: text/event-stream`,可带 `Last-Event-ID`(优先于 query用于断线续传。
- 查询参数
| 参数 | 默认 | 类型 | 说明 |
| --- | --- | --- | --- |
| after_seq | 0 | int | backlog 起始 seq若有 Last-Event-ID 则覆盖) |
| limit | 200 | int(≤500) | backlog 最多条数 |
- SSE 输出格式:
```
id: {seq}
event: message
data: {...message json...}
```
- `id` 为消息 `seq`,便于续传;`data` 为消息 JSON同追加消息响应字段
- Backlog建立连接后先补发 `seq > after_seq` 的消息order asc最多 `limit` 条),再进入实时订阅。
- 实时Redis channel `session:{session_id}:messages` 发布消息 IDSSE 侧读取后按 seq 去重、推送。
- 心跳:周期输出 `: ping` 保活(生产环境)。
- 错误401 未授权404 session 不存在。
## cURL 示例
```bash
@@ -93,4 +150,17 @@ curl -s -X POST http://localhost:8000/api/sessions/$SESSION_ID/messages \
# 增量查询
curl -s "http://localhost:8000/api/sessions/$SESSION_ID/messages?after_seq=0&limit=50" \
-H "Authorization: Bearer $TOKEN"
# 归档
curl -X POST http://localhost:8000/api/sessions/$SESSION_ID/archive \
-H "Authorization: Bearer $TOKEN"
# 获取单条消息
curl -s http://localhost:8000/api/sessions/$SESSION_ID/messages/{message_id} \
-H "Authorization: Bearer $TOKEN"
# SSE断线续传可带 Last-Event-ID
curl -N http://localhost:8000/api/sessions/$SESSION_ID/sse?after_seq=10 \
-H "Authorization: Bearer $TOKEN" \
-H "Accept: text/event-stream"
```

View File

@@ -111,6 +111,124 @@ paths:
$ref: '#/components/schemas/Error'
"401":
description: 未授权
/sessions/{session_id}/messages/{message_id}:
get:
tags: [ChatSession]
summary: 获取单条消息(校验 session_id
security:
- bearerAuth: []
parameters:
- in: path
name: session_id
required: true
schema:
type: string
format: uuid
- in: path
name: message_id
required: true
schema:
type: string
format: uuid
responses:
"200":
description: 消息详情
content:
application/json:
schema:
$ref: '#/components/schemas/MessageResource'
"401":
description: 未授权
"404":
description: 未找到或不属于该会话
/sessions/{session_id}/archive:
post:
tags: [ChatSession]
summary: 归档会话(设为 CLOSED幂等
security:
- bearerAuth: []
parameters:
- in: path
name: session_id
required: true
schema:
type: string
format: uuid
responses:
"200":
description: 归档成功(或已归档)
content:
application/json:
schema:
$ref: '#/components/schemas/ChatSession'
"401":
description: 未授权
"404":
description: 未找到
/sessions/{session_id}/sse:
get:
tags: [ChatSession]
summary: SSE 增量推送backlog + Redis 实时)
security:
- bearerAuth: []
parameters:
- in: path
name: session_id
required: true
schema:
type: string
format: uuid
- in: query
name: after_seq
schema:
type: integer
default: 0
description: backlog 起始 seq若有 Last-Event-ID 以其为准)
- in: query
name: limit
schema:
type: integer
default: 200
maximum: 500
responses:
"200":
description: text/event-stream SSE 流
content:
text/event-stream:
schema:
type: string
example: |
id: 1
event: message
data: {"message_id":"...","seq":1}
"401":
description: 未授权
"404":
description: 未找到
/sessions/{session_id}:
get:
tags: [ChatSession]
summary: 获取会话详情
security:
- bearerAuth: []
parameters:
- in: path
name: session_id
required: true
schema:
type: string
format: uuid
responses:
"200":
description: 会话详情
content:
application/json:
schema:
$ref: '#/components/schemas/ChatSession'
"401":
description: 未授权
"404":
description: 未找到
get:
tags: [ChatSession]
summary: 按 seq 增量查询消息