2025-12-24 20:15:28 +08:00
|
|
|
|
# 后端 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`。
|
2025-12-25 17:56:09 +08:00
|
|
|
|
- 若 `xcx_users` 中已经存在该 `UserKey`,系统 SHALL 在登录流程中**更新该记录的 `UpdateTime` 为当前服务器时间**(用于“最近登录/活跃”判断)。
|
2025-12-24 20:15:28 +08:00
|
|
|
|
|
|
|
|
|
|
#### 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` 且数据为空
|
|
|
|
|
|
|
2025-12-25 17:56:09 +08:00
|
|
|
|
### 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` 建立适当索引(若数据量很大)。
|
|
|
|
|
|
|
2025-12-24 20:15:28 +08:00
|
|
|
|
## Known Limitations
|
|
|
|
|
|
- 当前 JWT 配置存在,但认证中间件与授权标注未启用;接口默认可匿名访问。
|
|
|
|
|
|
- `MessageType` 的过滤条件存在实现差异:部分接口仅在 `MessageType == 1` 时追加过滤(不覆盖 2)。
|
|
|
|
|
|
- 文件上传未实现内容类型/大小的强制限制。
|