# 消息中心未读数量与全部已读接口需求

## 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. 所有接口继续使用项目现有统一鉴权、业务状态码和响应信封格式。

