# 后端 API（WxCheckMvc）规范

## Purpose
本能力描述 `WxCheckMvc` 暴露给微信小程序与管理侧的 HTTP API：登录/注册、会话记录的增删改查、文件上传、地址补全，以及 Redis Stream 的消息读取。

路由约定：统一为 `/api/{Controller}/{Action}`。

## Requirements

### Requirement: 小程序登录（基于微信 code）
系统 SHALL 提供登录接口，用 `wx.login()` 获取的 `code` 向微信换取 `openid`，并以 `openid` 作为系统内部 `UserKey`。

接口：`POST /api/Login/Login`

请求：
- `Code: string`

响应（成功）：
- `success: true`
- `data`: 用户对象（包含 `UserKey/openid` 与 `Token`）

行为：
- 若 `xcx_users` 不存在该 `UserKey`，系统 SHALL 自动插入一条用户记录（资料可为空）。
- 若用户 `IsDisabled = 1`，系统 SHALL 返回 `success: false`。
- 若 `xcx_users` 中已经存在该 `UserKey`，系统 SHALL 在登录流程中**更新该记录的 `UpdateTime` 为当前服务器时间**（用于“最近登录/活跃”判断）。

#### Scenario: 首次登录自动建档
- **WHEN** 提交的 `code` 能换取有效 `openid`
- **AND** 数据库中不存在该 `openid`
- **THEN** 系统创建用户记录并返回 `Token`

#### Scenario: 禁用用户登录
- **WHEN** 用户 `IsDisabled = 1`
- **THEN** 系统返回 `success: false` 且提示用户已禁用

### Requirement: 小程序注册/完善资料
系统 SHALL 提供接口用于完善用户资料（用户名、微信名、手机号、头像链接）。

接口：`POST /api/Login/Register`

请求：
- `UserKey: string`（openid）
- `UserName: string`
- `WeChatName: string`
- `PhoneNumber: string`
- `AvatarUrl: string`

校验：
- `UserKey` 必填
- `PhoneNumber` SHALL 清洗为纯数字后满足 `^1\d{10}$`
- `UserName` SHALL 去除标点/符号/空白后仍非空

响应（成功）：
- `success: true`
- `data`: 更新后的用户对象（包含 `Token`）

#### Scenario: 正常完善资料
- **WHEN** 提交合法的用户名与手机号
- **AND** `UserKey` 对应用户存在
- **THEN** 系统更新用户资料并返回新 `Token`

#### Scenario: 用户不存在
- **WHEN** `UserKey` 对应用户不存在
- **THEN** 系统返回 404

### Requirement: 上传文件并可更新头像
系统 SHALL 提供文件上传接口，保存到后端 `wwwroot/{rootPathType}` 下，并返回可访问 URL。

接口：`POST /api/Check/UploadFile`（`multipart/form-data`）

表单字段：
- `file`: 上传文件
- `rootPathType: string`（可选；默认 `Avatar`，仅允许字母/数字/下划线）
- `userKey: string`（可选；若提供则更新 `xcx_users.AvatarUrl`）

响应：
- `success: true/false`
- `url`: 公开访问 URL
- `path`: 相对路径

#### Scenario: 上传头像并更新用户表
- **WHEN** 上传文件并提供 `userKey`
- **THEN** 系统保存文件并更新该用户 `AvatarUrl`

### Requirement: 新增会话记录
系统 SHALL 提供接口新增会话记录（软实时写入数据库），并尝试将消息投递到 Redis Stream。

接口：`POST /api/Check/AddConversation`

请求（核心字段）：
- `UserKey: string`
- `ConversationContent: string`
- `SendMethod: string`
- `UserLocation: string`（当前实现中会尝试解析 `lat,lng`）
- `MessageType: int`（默认 1）
- `Guid?: string`（可选；缺省则服务端生成）
- `SpeakingTime?: int`

行为：
- 系统 SHALL 写入 `xcx_conversation`，并生成/使用 `Guid`。
- 系统 MAY 将会话与用户信息写入 Redis Stream（key: `xcx_msg`，group: `xcx_group`）。

#### Scenario: 新增会话并返回 guid
- **WHEN** 提交包含 `UserKey` 与 `ConversationContent` 的请求
- **THEN** 系统创建会话记录并返回 `conversationGuid`

### Requirement: 查询会话记录（按用户）
系统 SHALL 提供按 `UserKey` 查询会话记录的接口，默认只返回未删除记录。

接口：`POST /api/Check/GetConversations`

请求：
- `UserKey: string`
- `MessageType: int`（0 不过滤；1 公有；2 私有；当前实现仅在 `MessageType == 1` 时追加过滤）

#### Scenario: 查询用户所有未删除会话
- **WHEN** 提交 `UserKey`
- **THEN** 系统返回该用户 `IsDeleted = 0` 的会话记录

### Requirement: 分页查询会话记录
系统 SHALL 提供分页接口。

接口：`POST /api/Check/GetConversationsByPage`

请求：
- `UserKey: string`
- `Page: int`（<1 视为 1）
- `PageSize: int`（1..100，否则默认 10）
- `MessageType: int`（当前实现仅在 `MessageType == 1` 时追加过滤）

响应：
- `data.conversations`
- `data.totalCount / page / pageSize / totalPages`

#### Scenario: PageSize 超限
- **WHEN** `PageSize > 100`
- **THEN** 系统按默认值 10 处理

### Requirement: 更新会话记录
系统 SHALL 提供接口按 `Guid + UserKey` 更新会话内容与发送方式。

接口：`POST /api/Check/UpdateConversation`

请求：
- `Guid: string`
- `UserKey: string`
- `ConversationContent: string`
- `SendMethod: string`
- `MessageType: int`

#### Scenario: 非本人更新
- **WHEN** `Guid` 存在但 `UserKey` 不匹配
- **THEN** 系统返回 404（记录不存在或无权限修改）

### Requirement: 删除会话记录（软删除）
系统 SHALL 提供接口按 `Guid + UserKey` 软删除会话。

接口：`POST /api/Check/DeleteConversation`

请求：
- `Guid: string`
- `UserKey: string`

#### Scenario: 删除已删除记录
- **WHEN** 记录已被删除
- **THEN** 系统返回 404

### Requirement: 按 Guid 查询会话（不受软删除影响）
系统 SHALL 提供接口按 `Guid` 查询单条会话记录，不过滤 `IsDeleted`。

接口：`POST /api/Check/GetConversationByGuid`

#### Scenario: 查询已删除会话
- **WHEN** `Guid` 对应记录存在但 `IsDeleted = 1`
- **THEN** 系统仍返回该记录

### Requirement: 地址补全（经纬度→地址）
系统 SHALL 提供接口按 `Guid` 查找会话记录经纬度，并使用高德逆地理编码生成地址写回 `UserLocation`。

接口：`POST /api/Check/CheckAddress`

#### Scenario: 记录不存在
- **WHEN** `Guid` 对应记录不存在或已删除
- **THEN** 系统返回 404

### Requirement: Redis Stream 读取消息
系统 SHALL 提供接口从 Redis Stream 读取消息。

接口：`POST /api/Check/ReadMessageFromRedis`

请求：
- `GroupName?: string`（默认 `xcx_group`）
- `ConsumerName?: string`（默认 `consumer_{ticks}`）
- `Count?: int`（默认 1）

#### Scenario: 无新消息
- **WHEN** Stream 中无可读消息
- **THEN** 返回 `success: true` 且数据为空

### Requirement: 管理端统计接口（QueryStats）

系统 SHALL 提供聚合统计接口 `GET /api/Admin/QueryStats`，返回单行 JSON，`data` 对象字段如下：

- `ActiveUsers` (number)：最近 7 天内 `UpdateTime` >= DATE_SUB(NOW(), INTERVAL 7 DAY) 且 `UserKey` 和 `PhoneNumber` 都不为空的用户数
- `TotalConversations` (number)：`xcx_conversation` 的总记录数
- `TodayNewConversations` (number)：`xcx_conversation` 中 `CreateTime >= CURDATE() AND CreateTime < DATE_ADD(CURDATE(), INTERVAL 1 DAY)` 的记录数
- `TotalUsers` (number)：`xcx_users` 中 `UserKey` 和 `PhoneNumber` 都不为空的用户总数

响应（成功）：
- `success: true`
- `data`: { `ActiveUsers`, `TotalConversations`, `TodayNewConversations`, `TotalUsers` }

行为/安全：
- 当前实现为匿名访问，建议在后续迭代中加上管理员鉴权（例如 `[Authorize(Roles = "Admin")]` 或策略）。
- 为保证查询性能，建议为 `xcx_users.UpdateTime` 和 `xcx_conversation.CreateTime` 建立适当索引（若数据量很大）。

## Known Limitations
- 当前 JWT 配置存在，但认证中间件与授权标注未启用；接口默认可匿名访问。
- `MessageType` 的过滤条件存在实现差异：部分接口仅在 `MessageType == 1` 时追加过滤（不覆盖 2）。
- 文件上传未实现内容类型/大小的强制限制。
