feat: 接入消息未读数、全部已读与首页红点角标同步。

固定消息中心为首页入口,并在推送到达、已读后刷新桌面角标。

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
2026-07-21 18:02:43 +08:00
parent e7f1d777dd
commit caeeb9a1cf
18 changed files with 748 additions and 37 deletions

View File

@ -0,0 +1,204 @@
# 消息中心未读数量与全部已读接口需求
## 1. 需求背景
iOS App 需要实现以下功能:
1. 首页展示消息中心入口,有未读消息时显示红点。
2. 获取最新未读消息数量后,同步更新 App 桌面图标右上角的角标数量。
3. 用户点击某条未读消息并成功标记已读后,立即更新最新未读数量和桌面角标。
4. 消息中心导航栏右上角提供“全部已读”按钮。
5. 全部标记已读成功后,更新首页红点,并将桌面角标更新为服务端返回的最新未读数量。
## 2. 当前已有接口
### 2.1 获取消息列表
```http
GET /api/app/msg/list
```
当前参数:
| 参数 | 类型 | 必填 | 说明 |
| --- | --- | --- | --- |
| `last_id` | int | 否 | 分页游标,首次请求传 `0` |
| `limit` | int | 否 | 每页数量 |
| `unread` | int | 否 | `0` 获取全部消息,`1` 只获取未读消息 |
该接口仅返回分页消息,没有返回未读消息总数。客户端若通过分页遍历全部未读消息来统计数量,请求次数和数据流量都会随着未读消息数量增长,因此不适合作为桌面角标的常规数据来源。
### 2.2 标记单条消息已读
```http
POST /api/app/msg/read?id={message_id}
```
当前接口可以标记单条消息已读,但成功响应没有返回操作后的最新未读数量。
## 3. 需要新增或调整的接口
### 3.1 获取未读消息总数
建议新增:
```http
GET /api/app/msg/unread-count
```
#### 请求说明
- 不需要业务请求参数。
- 根据当前登录 Token 识别用户及业务账号。
- 返回当前账号下尚未读取的消息总数。
#### 成功响应示例
```json
{
"code": 100000,
"msg": "success",
"data": {
"unread_count": 12
}
}
```
#### 字段说明
| 字段 | 类型 | 必有 | 说明 |
| --- | --- | --- | --- |
| `unread_count` | int | 是 | 当前账号的最新未读消息数量,最小值为 `0` |
### 3.2 全部标记为已读
建议新增:
```http
POST /api/app/msg/read-all
```
#### 请求说明
- 不需要业务请求参数和请求体。
- 根据当前登录 Token 识别用户及业务账号。
- 将当前账号下请求执行时已经存在的所有未读消息标记为已读。
- 接口需要支持幂等调用;没有未读消息时仍返回成功。
#### 成功响应示例
```json
{
"code": 100000,
"msg": "success",
"data": {
"updated_count": 12,
"unread_count": 0
}
}
```
#### 字段说明
| 字段 | 类型 | 必有 | 说明 |
| --- | --- | --- | --- |
| `updated_count` | int | 是 | 本次实际被更新为已读的消息数量 |
| `unread_count` | int | 是 | 操作完成后的最新未读消息数量 |
> 客户端应以响应中的 `unread_count` 为准,不应直接假定其一定为 `0`。例如“全部已读”执行期间如果有新消息到达,操作完成后仍可能存在新的未读消息。
### 3.3 调整单条已读接口响应
保留现有接口:
```http
POST /api/app/msg/read?id={message_id}
```
建议成功响应增加最新未读数量:
```json
{
"code": 100000,
"msg": "success",
"data": {
"unread_count": 11
}
}
```
#### 字段说明
| 字段 | 类型 | 必有 | 说明 |
| --- | --- | --- | --- |
| `unread_count` | int | 是 | 单条消息标记已读后的最新未读消息数量 |
#### 幂等要求
- 对已经处于已读状态的消息重复调用时,接口仍返回成功和最新 `unread_count`
- 消息不存在或不属于当前账号时,返回明确的业务错误,不能修改其他账号的消息。
## 4. 统一约定
### 4.1 账号范围
- 所有接口必须根据登录 Token 限定当前用户及当前业务账号的数据范围。
- 景区账号、门店账号等不同业务账号之间的未读数量不能互相污染。
- 切换账号后,客户端会重新调用未读数量接口。
### 4.2 数值约束
- `unread_count``updated_count` 必须返回 JSON 整数。
- 数值不得为负数。
- 即使数量为 `0`,字段也必须存在,不能返回 `null`、空字符串或省略字段。
### 4.3 一致性与并发
- 单条已读或全部已读操作及其响应中的 `unread_count` 应基于同一账号范围。
- `read-all` 应尽量在事务内完成批量更新和剩余未读数统计。
- 如果操作期间有新消息写入,响应应返回操作完成时数据库中的真实 `unread_count`
- 客户端始终以最近一次成功接口响应中的 `unread_count` 更新首页红点和桌面角标。
### 4.4 错误响应
沿用现有统一业务响应结构,例如:
```json
{
"code": 100001,
"msg": "消息不存在或无权操作",
"data": null
}
```
建议至少区分以下情况:
- Token 无效或登录已过期。
- 单条消息不存在。
- 单条消息不属于当前账号。
- 数据库更新失败。
## 5. 客户端调用时机
| 场景 | 调用接口 | 客户端处理 |
| --- | --- | --- |
| 登录后首次进入首页 | `GET /msg/unread-count` | 更新首页红点和桌面角标 |
| App 回到前台 | `GET /msg/unread-count` | 校准最新未读数量 |
| 前台收到新消息推送 | `GET /msg/unread-count` | 更新首页红点和桌面角标 |
| 点击一条未读消息 | `POST /msg/read` | 使用返回的 `unread_count` 更新红点和桌面角标 |
| 点击“全部已读” | `POST /msg/read-all` | 刷新消息列表,使用返回的 `unread_count` 更新红点和桌面角标 |
| 切换业务账号 | `GET /msg/unread-count` | 使用新账号的数量覆盖旧账号角标 |
| 退出登录 | 无 | 客户端将桌面角标清零 |
## 6. 验收标准
1. 未读数量为 `0` 时,接口稳定返回 `{"unread_count": 0}`
2. 存在多页未读消息时,未读数量接口返回完整总数,而不是当前页数量。
3. 单条未读消息标记成功后,返回的 `unread_count` 与数据库实际剩余未读数一致。
4. 对同一消息重复调用单条已读接口不会重复扣减未读数量。
5. 全部已读接口可一次处理当前账号的所有未读消息,并返回实际更新数量。
6. 重复调用全部已读接口仍成功,返回 `updated_count = 0`
7. 不同业务账号的未读数量相互隔离。
8. 并发到达新消息时,响应中的 `unread_count` 能反映操作完成时的真实剩余数量。
9. 所有接口继续使用项目现有统一鉴权、业务状态码和响应信封格式。