Files
IM/frontend/pc/IM/API.md
T

574 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# IM_NEW 接口文档
> 源码基线:`IM_NEW/codex/api-alignment-fixes`(起点 `32177a7293ec9eb7bd731467953da3c51e561beb`
> 文档以控制器、DTO、枚举、序列化配置和 Nginx 网关配置为准;“当前实现注意事项”用于标明源码中的实际限制,不代表理想设计。
## 1. 通用约定
### 1.1 网关与路径大小写
- 网关默认端口:`8009`
- HTTP API 前缀:`/api`
- SignalR Hub`/chat`
- Nginx `location` 匹配区分大小写,因此应使用本文给出的全小写控制器前缀,例如 `/api/user/me``/api/User/Me` 在当前网关会返回 404。
### 1.2 认证
除登录、注册以及下文特别标注的接口外,请携带:
```http
Authorization: Bearer <JWT>
```
SignalR 客户端通过 `/chat?access_token=<JWT>` 完成握手;使用官方 SignalR 客户端时由 `accessTokenFactory` 自动添加。
Token 缺失、无效或过期时返回 **HTTP 401**,业务响应为:
```json
{ "code": 1006, "message": "认证失败", "data": null }
```
已认证但权限不足时返回 **HTTP 403**,业务码为 `1005`。新版前端在滚动发布期间仍兼容旧服务的 HTTP 200 + `code=1006`
### 1.3 统一响应
除文件内容下载外,接口通常返回:
```json
{
"code": 0,
"message": "成功",
"data": {}
}
```
- JSON 属性使用 camelCase。
- `code === 0` 表示业务成功。
- 参数校验失败也返回 HTTP 200,业务码为 `1003`
- 枚举通过 `JsonStringEnumConverter` 序列化为字符串,并接受字符串枚举请求值。
- 未处理异常统一返回 HTTP 500 + `code=1000`,响应头 `X-Correlation-ID` 可用于关联服务端日志。
## 2. 认证与用户
### 2.1 POST `/api/auth/login`
无需认证。请求:
```json
{ "userName": "user1", "password": "******" }
```
校验:`userName` 520 字符;`password` 非空且不超过 50 字符。
成功返回 `Result<LoginResponse>`
```json
{
"code": 0,
"message": "成功",
"data": {
"userId": "guid",
"token": "jwt",
"refreshToken": "string",
"expired": null,
"userName": "string",
"nickName": "string",
"avatar": "string|null",
"creationTime": "datetime-offset"
}
}
```
当前实现创建 `LoginResponse` 时将 `expired` 传为 `null`
### 2.2 POST `/api/auth/register`
无需认证。请求:
```json
{ "userName": "newuser", "password": "******", "nickName": "新用户" }
```
校验:`userName` 520 字符;`password` 650 字符;`nickName` 非空且不超过 50 字符。返回 `Result<UserResponse>`
### 2.3 POST `/api/auth/refresh`
无需 Access Token。请求:
```json
{ "refreshToken": "string" }
```
返回新的 `Result<LoginResponse>`
### 2.4 用户接口
| 方法 | 路径 | 参数 | 返回 |
|---|---|---|---|
| GET | `/api/user/me` | 无 | `Result<UserResponse>` |
| GET | `/api/user/find?userId={guid}` | query: `userId` | `Result<UserResponse>` |
| GET | `/api/user/findByUname?username={value}` | query: `username` | `Result<UserResponse>` |
| POST | `/api/user/update` | body: `UserUpdateRequest` | `Result<UserResponse>` |
| POST | `/api/user/getUsersByIds` | body: GUID 数组 | `Result<UserResponse[]>` |
`UserUpdateRequest` 的字段均可省略:
```json
{
"nickName": "string|null",
"region": "string|null",
"avatar": "string|null",
"description": "string|null"
}
```
`UserResponse`
```json
{
"id": "guid",
"userName": "string",
"nickName": "string",
"email": "string|null",
"phone": "string|null",
"region": "string",
"description": "string",
"avatar": "string|null",
"creationTime": "datetime-offset",
"deletion": "datetime-offset|null"
}
```
## 3. 好友与好友申请
所有接口均要求认证。
### 3.1 好友
| 方法 | 路径 | 参数 | 返回 |
|---|---|---|---|
| GET | `/api/friend/list` | 无 | `Result<FriendResponse[]>` |
| POST | `/api/friend/delete?friendId={guid}` | 好友关系记录 ID,不是对方用户 ID | `Result<object>`,成功时 `data: null` |
| POST | `/api/friend/block?friendId={guid}` | 好友关系记录 ID | `Result<object>` |
| GET | `/api/friend/checkFriend?userId={guid}&targetId={guid}` | 两个用户 ID | `Result<boolean>` |
`FriendResponse`
```json
{
"id": "friend-relation-guid",
"targetId": "peer-user-guid",
"avatar": "string|null",
"nickName": "string",
"remarkName": "string|null",
"createTime": "datetime",
"updateTime": "datetime|null",
"status": "Pending|Added|Declined|Blocked"
}
```
### 3.2 好友申请
#### POST `/api/friendRequest/add`
```json
{
"targetId": "guid",
"description": "string|null",
"remarkName": "string|null"
}
```
`targetId` 必填。返回 `Result<FriendRequestResponse>`
#### POST `/api/friendRequest/handle`
```json
{
"requestId": "guid",
"action": "Accpet|Reject|Block",
"remarkName": "string|null"
}
```
注意:后端枚举当前拼写为 `Accpet`,接受申请时 `remarkName` 必填。
#### GET `/api/friendRequest/list`
返回与当前用户相关的申请:`Result<FriendRequestResponse[]>`
`FriendRequestResponse.state``Pending``Declined``Passed``Blocked`
## 4. 群组
### 4.1 群信息
| 方法 | 路径 | 请求 | 返回 |
|---|---|---|---|
| GET | `/api/group/getAll` | 无 | `Result<GroupResponse[]>` |
| GET | `/api/group/getOne?groupId={guid}` | query: `groupId` | `Result<GroupResponse>` |
| POST | `/api/group/create` | `{ "name": "string|null" }` | `Result<GroupResponse>` |
| POST | `/api/group/update` | `GroupUpdateRequest` | `Result<GroupResponse>` |
| POST | `/api/group/dissolve?groupId={guid}` | 群 ID;仅群主 | `Result<object>` |
`name` 最大 20 字符。更新请求:
```json
{
"groupId": "guid",
"groupName": "string|null",
"avatar": "string|null",
"description": "string|null"
}
```
`description` 实际用于更新群公告 `announcement`
`GroupResponse`
```json
{
"id": "guid",
"name": "string",
"groupMaster": "guid",
"authority": "REQUIRE_CONSENT|ANYONE_CAN_JOIN|NOT_ALLOWED_TO_JOIN",
"allMembersBanned": false,
"status": "Normal|Blocked",
"announcement": "string",
"avatar": "string|null",
"maxSequenceId": 0,
"lastMessage": "string",
"lastSenderName": "string",
"created": "datetime-offset",
"updated": "datetime-offset"
}
```
`getAll` 按当前用户的有效群成员关系返回群,普通成员、管理员和群主均可看到已加入群。
### 4.2 群成员
| 方法 | 路径 | 参数 | 返回 |
|---|---|---|---|
| GET | `/api/groupMember/checkMember?userId={guid}&groupId={guid}` | 用户 ID、群 ID | `Result<boolean>` |
| GET | `/api/groupMember/list?groupId={guid}` | 群 ID | `Result<GroupMemberResponse[]>` |
| POST | `/api/groupMember/delete?memberId={guid}` | 群成员记录 ID | `Result<object>` |
| POST | `/api/groupMember/leave?groupId={guid}` | 群 ID | `Result<object>` |
`GroupMemberResponse.role``Normal``Administrator``Master`
`list``delete``leave` 均要求登录。`delete` 是管理操作,只能移除角色低于操作者的其他成员,禁止移除群主或自己;群主必须使用 `dissolve`,不能使用 `leave`
`checkMember` 是服务间内部接口,必须携带 `X-Internal-Api-Key`,不应通过公网网关暴露。
### 4.3 群邀请
| 方法 | 路径 | 参数 | 返回 |
|---|---|---|---|
| POST | `/api/groupInvitation/send` | body: `{ "groupId": "guid", "userId": "guid" }` | `Result<GroupInvitationResponse>` |
| GET | `/api/groupInvitation/get?invitationId={guid}` | 邀请 ID | `Result<GroupInvitationResponse>` |
| POST | `/api/groupInvitation/handle?invitationId={guid}&action={value}` | `action=Accept|Reject` | `Result<object>` |
邀请状态:`Pending``Passed``Reject`
### 4.4 入群申请
| 方法 | 路径 | 参数 | 返回 |
|---|---|---|---|
| POST | `/api/groupRequest/send` | body: `{ "groupId": "guid", "desc": "string|null" }` | `Result<GroupRequestResponse>` |
| POST | `/api/groupRequest/handle?requestId={guid}&action={value}` | `action=Accept|Reject` | `Result<object>` |
| GET | `/api/groupRequest/find?id={guid}` | 申请 ID | `Result<GroupRequestResponse>` |
| GET | `/api/groupRequest/list` | 无 | `Result<GroupRequestResponse[]>` |
`desc` 最大 20 字符;状态为 `Pending``Declined``Passed`
`list` 返回当前用户提交的申请,以及当前用户作为管理员或群主有权处理的群申请。
## 5. 会话与消息
所有接口均要求认证。
### 5.1 会话
| 方法 | 路径 | 参数 | 返回 |
|---|---|---|---|
| GET | `/api/conversation/list` | 无 | `Result<ConversationResponse[]>` |
| GET | `/api/conversation/get?id={guid}` | 会话 ID | `Result<ConversationResponse>` |
| POST | `/api/conversation/markRead?conversationId={guid}` | 会话 ID | `Result<object>` |
`ConversationResponse`
```json
{
"id": "guid",
"userId": "guid",
"targetId": "guid",
"targetAvatar": "string",
"targetName": "string",
"lastReadSequenceId": 0,
"unreadCount": 0,
"chatType": "PRIVATE|GROUP",
"lastMessage": "string",
"dateTime": "datetime"
}
```
### 5.2 POST `/api/message/send`
```json
{
"clientMsgId": "guid",
"targetId": "guid",
"chatType": "PRIVATE|GROUP",
"msgType": "Text|Image|Voice|Video|File|VoiceChat|VideoChat",
"quoteMessageId": "guid|null",
"ext": { "key": "value" },
"text": "string|null",
"url": "string|null",
"width": 0,
"height": 0,
"thumb": "string|null",
"duration": 0,
"fileId": "guid|null",
"fileName": "string|null",
"fileSize": 0,
"fileFormat": "mime/type|null"
}
```
- `clientMsgId``targetId` 必填。
- `Text` 要求 `text`
- `Image``Video``Voice` 至少提供 `url``fileId`
- `File` 要求 `fileId``fileName``fileSize``fileFormat`;服务端已支持构建和保存文件消息。
- `VoiceChat``VideoChat` 仍未实现,发送会返回 `2303`
成功返回 `Result<MessageResponse>`
```json
{
"id": "guid",
"clientMsgId": "guid",
"chatType": "PRIVATE",
"msgType": "Text",
"senderId": "guid",
"targetId": "guid",
"state": "Sent|Withdrwan",
"streamKey": "string",
"sequenceId": 1,
"creationTime": "datetime-offset",
"content": {
"fallback": "string",
"body": {},
"ext": {},
"quote": null
}
}
```
### 5.3 其他消息接口
| 方法 | 路径 | 参数 | 返回 |
|---|---|---|---|
| POST | `/api/message/withDraw?msgId={guid}` | 消息 ID | `Result<object>` |
| GET | `/api/message/getMessages?conversationId={guid}&cursor={long?}&direction={int}&limit={int}` | 会话、游标、方向、条数 | `Result<GetMessagesResponse>` |
`GetMessagesResponse`
```json
{ "messages": [], "hasmore": false }
```
方向约定:`0` 查询 `SequenceId < cursor` 的历史消息;`1` 查询 `SequenceId > cursor` 的增量消息。两个方向都查询 `limit + 1` 条判断 `hasmore`,但响应最多返回 `limit` 条并按序列号升序;`limit` 范围为 1100。
## 6. 文件服务
所有接口均要求认证。
### 6.1 文件
#### POST `/api/file/simple-upload`
`multipart/form-data`
| 字段 | 类型 | 必填 |
|---|---|---|
| `file` | 文件 | 是 |
| `isPublic` | boolean | 是 |
服务端计算 MD5 并执行安全秒传:公开文件可复用,私有文件只允许同一所有者复用。返回 `Result<FileResponse>`
#### GET `/api/file/{id}`
返回文件信息 `Result<FileResponse>`
#### GET `/api/file/{id}/content`
返回鉴权后的二进制文件流,支持 HTTP Range,不使用 `Result<T>` 包装。无权限返回 HTTP 403,文件不存在返回 HTTP 404。
`FileResponse` 为扁平结构,不暴露存储桶、对象键等内部位置:
```json
{
"id": "guid",
"ownerId": "guid",
"fileName": "avatar.png",
"fileSize": 123,
"contentType": "image/png",
"state": "Uploaded",
"checkSum": "md5-hex",
"chatType": "PRIVATE|GROUP|null",
"targetId": "guid|null",
"isPublic": false,
"created": "datetime-offset",
"updated": "datetime-offset",
"url": "string|null"
}
```
`url` 仅在存储提供方能够生成公开地址时存在。
### 6.2 分片上传
#### POST `/api/fileTask/init`
```json
{
"conversationId": "guid",
"chatType": "PRIVATE|GROUP",
"targetId": "peer-or-group-guid",
"fileName": "string",
"fileSize": 123,
"contentType": "mime/type",
"checkSum": "md5"
}
```
返回:
```json
{
"taskId": "guid",
"uploadSessionId": "string",
"instant": false,
"uploadMode": "LocalMultipart|Presigned",
"totalPartCount": 1,
"partSizeBytes": 5242880,
"file": null
}
```
秒传命中时返回 `instant: true``uploadMode: "Instant"` 和最终 `file`;客户端直接使用该文件,不再调用 `complete`
#### GET `/api/fileTask/getuploadurl?sessionId={value}&partNum={n}`
返回 `Result<PresignedUrl>`,不是字符串:
```json
{
"url": "string",
"method": "PUT|POST",
"headers": {},
"expiresAt": "datetime-offset"
}
```
#### GET `/api/fileTask/progress?sessionId={value}`
```json
{
"sessionId": "string",
"taskId": "string",
"fileSize": 123,
"totalPartCount": 1,
"completedPartCount": 0,
"uploadedBytes": 0,
"progressPercent": 0
}
```
#### POST `/api/fileTask/local/parts/upload`
`multipart/form-data``sessionId``partNumber``file`。返回 `Result<CompleteUploadResult>`,其中包含 `location``eTag``size``checksum``versionId`
#### POST `/api/fileTask/complete`
```json
{
"sessionId": "string",
"parts": [
{ "partNumber": 1, "eTag": "string", "size": 123, "checksum": null }
]
}
```
成功接受异步合并时返回 HTTP 202 + `Result<UploadTaskResponse>`,其中 `state``Merging`
#### GET `/api/fileTask/status?taskId={guid}`
客户端轮询此接口。处理中返回 `Uploading|Merging`;失败返回 `state: "Failed"``failureReason`;完成后返回:
```json
{
"id": "upload-task-guid",
"uploaderId": "guid",
"conversationId": "guid",
"fileName": "string",
"fileSize": 123,
"contentType": "mime/type",
"storageLocation": {},
"state": "Completed",
"checkSum": "string",
"resultFileId": "guid",
"failureReason": null,
"file": { "id": "guid", "fileName": "string", "fileSize": 123, "url": null }
}
```
任务、分片、进度、完成和状态接口都会校验当前用户是上传者。私有文件的 `url` 为 null,使用 `/api/file/{id}/content` 鉴权读取。
## 7. SignalR
- Hub`/chat`
- 服务端事件:`ReceiveNewMessage`
- 当前 Hub 没有 `clearUnreadCount` 方法;清零未读应调用 HTTP `/api/conversation/markRead`
推送载荷与 HTTP `MessageResponse` 略有不同:
```json
{
"id": "guid",
"clientId": "guid",
"chatType": "PRIVATE|GROUP",
"msgType": "Text|Image|Voice|Video",
"senderId": "guid",
"targetId": "guid",
"state": "Sent|Withdrwan",
"streamKey": "string",
"sequenceId": 1,
"pushTimestamp": 0,
"content": {
"fallback": "string",
"body": {},
"ext": {},
"quote": null
}
}
```
## 8. 业务状态码
| 范围/代码 | 含义 |
|---|---|
| `0` | 成功 |
| `1000``1006` | 系统、超时、参数、数据库、权限、认证错误 |
| `2000``2004` | 用户不存在、已存在、密码错误、禁用、登录过期 |
| `2100``2107` | 好友申请、好友关系和操作错误 |
| `2200``2206` | 群不存在、已入群、群满、权限、邀请、申请、成员错误 |
| `2300``2303` | 消息发送、消息不存在、撤回、不支持的消息类型 |
| `2400``2403` | 文件上传、不存在、过大、类型不支持 |
| `3000``3004` | 管理后台错误 |
| `3100` | 会话不存在 |
| `3201``3206` | 分片不存在、合并失败、分片过小/数量不符、会话过期、分片号无效 |
完整名称与中文说明以 `IM.Commons/ResultCode.cs` 为准。