This commit is contained in:
2026-08-31 14:42:22 +08:00
72 changed files with 2479 additions and 168 deletions
+947
View File
@@ -0,0 +1,947 @@
# IM_API_NEW 接口文档
基于微服务架构的即时通讯系统,包含 6 个独立 WebApi 服务。
## 通用说明
### 统一响应格式 `Result<T>`
```json
{
"code": 0,
"message": "成功",
"data": { }
}
```
- `code`:业务状态码,`0` 表示成功(见下方状态码表)
- `message`:状态描述
- `data`:业务数据,失败时为 `null`
### 认证
除特别说明外,所有接口需在请求头携带 JWT:
```
Authorization: Bearer {token}
```
当前用户 ID 从 Token 的 `NameIdentifier` 声明中获取,无需在请求体重复传递。
### 路由约定
控制器统一采用 `api/[controller]/[action]` 路由模板(FileService 例外,见对应章节)。
---
## 1. 认证服务 (User.WebApi - Auth)
> 以下接口**无需认证**。
### POST /api/Auth/Login — 登录
请求体:
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| userName | string | 是 | 5-20 字符 |
| password | string | 是 | ≤50 字符 |
响应:`Result<LoginResponse>`
请求示例:
```json
{
"userName": "zhangsan",
"password": "P@ssw0rd"
}
```
响应示例:
```json
{
"code": 0,
"message": "成功",
"data": {
"userId": "8f3a2c10-1b2c-4d5e-9a8b-7c6d5e4f3a2b",
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d",
"expired": "2026-06-27T10:30:00Z",
"userName": "zhangsan",
"nickName": "张三",
"avatar": "https://cdn.example.com/avatar/zhangsan.png",
"creationTime": "2026-01-15T08:00:00+00:00"
}
}
```
失败示例(密码错误,code 2002):
```json
{
"code": 2002,
"message": "密码错误",
"data": null
}
```
### POST /api/Auth/Register — 注册
请求体:
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| userName | string | 是 | 5-20 字符 |
| password | string | 是 | 6-50 字符 |
| nickName | string | 是 | ≤50 字符 |
响应:`Result<UserResponse>`
请求示例:
```json
{
"userName": "zhangsan",
"password": "P@ssw0rd",
"nickName": "张三"
}
```
响应示例:
```json
{
"code": 0,
"message": "成功",
"data": {
"id": "8f3a2c10-1b2c-4d5e-9a8b-7c6d5e4f3a2b",
"userName": "zhangsan",
"nickName": "张三",
"email": null,
"phone": null,
"region": "",
"description": "",
"avatar": null,
"creationTime": "2026-06-26T08:00:00+00:00",
"deletion": null
}
}
```
### POST /api/Auth/Refresh — 刷新令牌
请求体:
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| refreshToken | string | 是 | 刷新令牌 |
响应:`Result<LoginResponse>`
请求示例:
```json
{
"refreshToken": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d"
}
```
响应示例:同 Login 的 `Result<LoginResponse>`
**LoginResponse 结构**
```json
{
"userId": "guid", "token": "string", "refreshToken": "string",
"expired": "datetime", "userName": "string", "nickName": "string",
"avatar": "string|null", "creationTime": "datetime"
}
```
---
## 2. 用户服务 (User.WebApi - User)
> 需认证。
### GET /api/User/Me — 当前用户信息
响应:`Result<UserResponse>`
响应示例:
```json
{
"code": 0,
"message": "成功",
"data": {
"id": "8f3a2c10-1b2c-4d5e-9a8b-7c6d5e4f3a2b",
"userName": "zhangsan",
"nickName": "张三",
"email": "zhangsan@example.com",
"phone": "13800000000",
"region": "广东·深圳",
"description": "这个人很懒,什么都没写",
"avatar": "https://cdn.example.com/avatar/zhangsan.png",
"creationTime": "2026-01-15T08:00:00+00:00",
"deletion": null
}
}
```
### GET /api/User/Find?userId={guid} — 查询指定用户
响应:`Result<UserResponse>`(结构同上)
### GET /api/User/FindByUname?username={string} — 按用户名查询
响应:`Result<UserResponse>`(结构同上)
### POST /api/User/Update — 更新资料
请求体(均可选):`nickName``region``avatar``description`
响应:`Result<UserResponse>`
请求示例:
```json
{
"nickName": "张三丰",
"region": "湖北·武当山",
"avatar": "https://cdn.example.com/avatar/new.png",
"description": "太极宗师"
}
```
### POST /api/User/GetUsersByIds — 批量查询用户
请求体:`["guid", "guid"]`Guid 数组)
响应:`Result<List<UserResponse>>`
请求示例:
```json
[
"8f3a2c10-1b2c-4d5e-9a8b-7c6d5e4f3a2b",
"1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
]
```
响应示例:
```json
{
"code": 0,
"message": "成功",
"data": [
{ "id": "8f3a2c10-1b2c-4d5e-9a8b-7c6d5e4f3a2b", "userName": "zhangsan", "nickName": "张三", "email": null, "phone": null, "region": "广东·深圳", "description": "", "avatar": null, "creationTime": "2026-01-15T08:00:00+00:00", "deletion": null }
]
}
```
**UserResponse 结构**
```json
{
"id": "guid", "userName": "string", "nickName": "string",
"email": "string|null", "phone": "string|null", "region": "string",
"description": "string", "avatar": "string|null",
"creationTime": "datetime", "deletion": "datetime|null"
}
```
---
## 3. 联系人服务 (ContactService.WebApi)
> 需认证。
### 好友 (Friend)
| 方法 | 路径 | 说明 | 参数 |
|------|------|------|------|
| GET | /api/Friend/List | 好友列表 | - |
| POST | /api/Friend/Delete | 删除好友 | `friendId` (query) |
| POST | /api/Friend/Block | 拉黑好友 | `friendId` (query) |
| GET | /api/Friend/CheckFriend | 检查好友关系 | `userId`, `targetId` (query) |
`GET /api/Friend/List` 响应示例:
```json
{
"code": 0,
"message": "成功",
"data": [
{
"id": "e5f6a7b8-c9d0-1e2f-3a4b-5c6d7e8f9a0b",
"targetId": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"avatar": "https://cdn.example.com/avatar/lisi.png",
"nickName": "李四",
"remarkName": "老李",
"createTime": "2026-02-01T10:00:00",
"updateTime": null,
"status": "Pending"
}
]
}
```
`POST /api/Friend/Delete?friendId={guid}` 响应示例:
```json
{ "code": 0, "message": "成功", "data": true }
```
`GET /api/Friend/CheckFriend?userId={guid}&targetId={guid}` 响应示例:
```json
{ "code": 0, "message": "成功", "data": true }
```
### 好友请求 (FriendRequest)
**POST /api/FriendRequest/Add** — 发起好友申请
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| targetId | guid | 是 | 被申请人 |
| description | string | 否 | 附言 |
| remarkName | string | 否 | 备注名 |
请求示例:
```json
{
"targetId": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"description": "我是张三,加个好友",
"remarkName": "老李"
}
```
响应示例:
```json
{
"code": 0,
"message": "成功",
"data": {
"id": "c1d2e3f4-a5b6-7c8d-9e0f-1a2b3c4d5e6f",
"ownerId": "8f3a2c10-1b2c-4d5e-9a8b-7c6d5e4f3a2b",
"ownerNickName": "张三",
"ownerAvatar": "https://cdn.example.com/avatar/zhangsan.png",
"targetId": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"targetNickName": "李四",
"targetAvatar": "https://cdn.example.com/avatar/lisi.png",
"description": "我是张三,加个好友",
"state": "Pending",
"remarkName": "老李",
"creationTime": "2026-06-26T09:00:00+00:00",
"deletion": null,
"modificationTime": null
}
}
```
**POST /api/FriendRequest/Handle** — 处理申请
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| requestId | guid | 是 | 请求 ID |
| action | string | 是 | `"Accpet"` 同意, `"Reject"` 拒绝, `"Block"` 拉黑 |
| remarkName | string | 同意时必填 | 备注名 |
请求示例:
```json
{
"requestId": "c1d2e3f4-a5b6-7c8d-9e0f-1a2b3c4d5e6f",
"action": "Accpet",
"remarkName": "张三"
}
```
响应示例:
```json
{ "code": 0, "message": "成功", "data": true }
```
**GET /api/FriendRequest/List** — 我相关的申请列表
响应示例:
```json
{
"code": 0,
"message": "成功",
"data": [
{
"id": "c1d2e3f4-a5b6-7c8d-9e0f-1a2b3c4d5e6f",
"ownerId": "8f3a2c10-1b2c-4d5e-9a8b-7c6d5e4f3a2b",
"ownerNickName": "张三",
"ownerAvatar": "https://cdn.example.com/avatar/zhangsan.png",
"targetId": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"targetNickName": "李四",
"targetAvatar": "https://cdn.example.com/avatar/lisi.png",
"description": "我是张三,加个好友",
"state": "Passed",
"remarkName": "老李",
"creationTime": "2026-06-26T09:00:00+00:00",
"deletion": null,
"modificationTime": "2026-06-26T09:05:00+00:00"
}
]
}
```
**FriendRequestResponse 状态 (State)**`"Pending"` 待通过, `"Declined"` 已拒绝, `"Passed"` 已同意, `"Blocked"` 已拉黑
---
## 4. 群组服务 (GroupService.WebApi)
> 需认证(GroupMember 部分接口除外)。
### 群组 (Group)
| 方法 | 路径 | 说明 | 参数 |
|------|------|------|------|
| GET | /api/Group/GetAll | 我加入的群列表 | - |
| GET | /api/Group/GetOne | 群详情 | `groupId` (query) |
| POST | /api/Group/Create | 创建群 | body: `name` (≤20) |
| POST | /api/Group/Update | 更新群 | body: 见下 |
Update 请求体:
| 字段 | 类型 | 必填 |
|------|------|------|
| groupId | guid | 是 |
| groupName | string | 否 |
| avatar | string | 否 |
| description | string | 否 |
`POST /api/Group/Create` 请求示例:
```json
{ "name": "技术交流群" }
```
`POST /api/Group/Update` 请求示例:
```json
{
"groupId": "a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d",
"groupName": "技术交流群(2026)",
"avatar": "https://cdn.example.com/group/tech.png",
"description": "欢迎交流技术"
}
```
`GET /api/Group/GetOne?groupId={guid}` 响应示例:
```json
{
"code": 0,
"message": "成功",
"data": {
"id": "a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d",
"name": "技术交流群",
"groupMaster": "8f3a2c10-1b2c-4d5e-9a8b-7c6d5e4f3a2b",
"authority": "REQUIRE_CONSENT",
"allMembersBanned": false,
"status": "Normal",
"announcement": "欢迎加入",
"avatar": "https://cdn.example.com/group/tech.png",
"maxSequenceId": 1024,
"lastMessage": "晚上好",
"lastSenderName": "张三",
"created": "2026-03-01T08:00:00+00:00",
"updated": "2026-06-26T09:00:00+00:00"
}
}
```
### 群成员 (GroupMember)
| 方法 | 路径 | 说明 | 参数 |
|------|------|------|------|
| GET | /api/GroupMember/CheckMember | 检查成员 | `userId`, `groupId` (query) |
| GET | /api/GroupMember/List | 成员列表 | `groupId` (query) |
| POST | /api/GroupMember/Delete | 移除成员 | `memberId` (query) |
`GET /api/GroupMember/List?groupId={guid}` 响应示例:
```json
{
"code": 0,
"message": "成功",
"data": [
{
"id": "f1e2d3c4-b5a6-9788-1a2b-3c4d5e6f7a8b",
"userId": "8f3a2c10-1b2c-4d5e-9a8b-7c6d5e4f3a2b",
"groupNickName": "群主张三",
"avatar": "https://cdn.example.com/avatar/zhangsan.png",
"groupId": "a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d",
"role": "Master",
"created": "2026-03-01T08:00:00+00:00"
}
]
}
```
**成员角色 (Role)**`"Normal"` 普通成员, `"Administrator"` 管理员, `"Master"` 群主
### 群邀请 (GroupInvitation)
| 方法 | 路径 | 说明 | 参数 |
|------|------|------|------|
| POST | /api/GroupInvitation/Send | 发送邀请 | body: `groupId`, `userId` |
| GET | /api/GroupInvitation/Get | 邀请详情 | `invitationId` (query) |
| POST | /api/GroupInvitation/Handle | 处理邀请 | `invitationId`, `action` (query) |
`POST /api/GroupInvitation/Send` 请求示例:
```json
{
"groupId": "a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d",
"userId": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
```
`GET /api/GroupInvitation/Get?invitationId={guid}` 响应示例:
```json
{
"code": 0,
"message": "成功",
"data": {
"id": "b2c3d4e5-f6a7-8b9c-0d1e-2f3a4b5c6d7e",
"groupId": "a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d",
"groupAvatar": "https://cdn.example.com/group/tech.png",
"groupName": "技术交流群",
"userId": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"userAvatar": "https://cdn.example.com/avatar/lisi.png",
"userNickName": "李四",
"operatorId": "8f3a2c10-1b2c-4d5e-9a8b-7c6d5e4f3a2b",
"operatorName": "张三",
"operatorAvatar": "https://cdn.example.com/avatar/zhangsan.png",
"state": "Pending",
"created": "2026-06-26T09:00:00+00:00",
"updated": "2026-06-26T09:00:00+00:00"
}
}
```
**邀请处理 action**`"Accept"` 接受, `"Reject"` 拒绝
**邀请状态 (State)**`"Pending"` 待被邀请人同意, `"Passed"` 已同意, `"Reject"` 拒绝
### 入群请求 (GroupRequest)
| 方法 | 路径 | 说明 | 参数 |
|------|------|------|------|
| POST | /api/GroupRequest/Send | 申请入群 | body: `groupId`, `desc` (≤20) |
| POST | /api/GroupRequest/Handle | 处理申请 | `requestId`, `action` (query) |
| GET | /api/GroupRequest/Find | 申请详情 | `id` (query) |
| GET | /api/GroupRequest/List | 申请列表 | - |
`POST /api/GroupRequest/Send` 请求示例:
```json
{
"groupId": "a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d",
"desc": "我想加入学习"
}
```
`GET /api/GroupRequest/Find?id={guid}` 响应示例:
```json
{
"code": 0,
"message": "成功",
"data": {
"id": "c3d4e5f6-a7b8-9c0d-1e2f-3a4b5c6d7e8f",
"groupId": "a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d",
"groupAvatar": "https://cdn.example.com/group/tech.png",
"groupName": "技术交流群",
"userId": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"userAvatar": "https://cdn.example.com/avatar/lisi.png",
"userNickName": "李四",
"operatorId": "00000000-0000-0000-0000-000000000000",
"operatorName": "",
"operatorAvatar": null,
"state": "Pending",
"description": "我想加入学习",
"created": "2026-06-26T09:00:00+00:00",
"updated": "2026-06-26T09:00:00+00:00"
}
}
```
**入群处理 action**`"Accept"` 接受, `"Reject"` 拒绝
**入群状态 (State)**`"Pending"` 待管理员同意, `"Declined"` 已拒绝, `"Passed"` 已通过
**群权限 (Authority)**`"REQUIRE_CONSENT"` 需管理员同意, `"ANYONE_CAN_JOIN"` 任意人可加, `"NOT_ALLOWED_TO_JOIN"` 不允许加入
**群状态 (Status)**`"Normal"` 正常, `"Blocked"` 封禁
---
## 5. 消息服务 (MessageService.WebApi)
> 需认证。
### 会话 (Conversation)
| 方法 | 路径 | 说明 | 参数 |
|------|------|------|------|
| GET | /api/Conversation/List | 会话列表 | - |
| GET | /api/Conversation/Get | 会话详情 | `id` (query) |
| POST | /api/Conversation/MarkRead | 清零未读 | `conversationId` (query) |
`GET /api/Conversation/List` 响应示例:
```json
{
"code": 0,
"message": "成功",
"data": [
{
"id": "d4e5f6a7-b8c9-0d1e-2f3a-4b5c6d7e8f9a",
"userId": "8f3a2c10-1b2c-4d5e-9a8b-7c6d5e4f3a2b",
"targetId": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"targetAvatar": "https://cdn.example.com/avatar/lisi.png",
"targetName": "李四",
"lastReadSequenceId": 1020,
"unreadCount": 4,
"chatType": "PRIVATE",
"lastMessage": "晚上一起吃饭吗?",
"dateTime": "2026-06-26T18:30:00"
}
]
}
```
`POST /api/Conversation/MarkRead?conversationId={guid}` 响应示例:
```json
{ "code": 0, "message": "成功", "data": null }
```
### 消息 (Message)
**POST /api/Message/Send** — 发送消息
| 字段 | 类型 | 必填 | 说明 |
|------|------|------|------|
| clientMsgId | guid | 是 | 客户端消息 ID(去重) |
| targetId | guid | 是 | 接收方(单聊=用户,群聊=群) |
| chatType | string | 是 | `"PRIVATE"` 单聊, `"GROUP"` 群聊 |
| msgType | string | 是 | 见消息类型表 |
| quoteMessageId | guid | 否 | 引用消息 ID |
| ext | object | 否 | 扩展字段 (键值对) |
| text | string | 否 | 文本内容 |
| url | string | 否 | 媒体 URL |
| width / height | int | 否 | 图片/视频尺寸 |
| thumb | string | 否 | 缩略图 |
| duration | int | 否 | 时长(语音/视频) |
文本消息请求示例:
```json
{
"clientMsgId": "7e8f9a0b-1c2d-3e4f-5a6b-7c8d9e0f1a2b",
"targetId": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"chatType": "PRIVATE",
"msgType": "Text",
"text": "晚上一起吃饭吗?"
}
```
图片消息请求示例:
```json
{
"clientMsgId": "8f9a0b1c-2d3e-4f5a-6b7c-8d9e0f1a2b3c",
"targetId": "a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d",
"chatType": "GROUP",
"msgType": "Image",
"url": "https://cdn.example.com/img/photo.jpg",
"width": 1920,
"height": 1080,
"thumb": "https://cdn.example.com/img/photo_thumb.jpg",
"quoteMessageId": "1c2d3e4f-5a6b-7c8d-9e0f-1a2b3c4d5e6f",
"ext": { "source": "album" }
}
```
发送响应示例:
```json
{
"code": 0,
"message": "成功",
"data": {
"id": "9a0b1c2d-3e4f-5a6b-7c8d-9e0f1a2b3c4d",
"clientMsgId": "7e8f9a0b-1c2d-3e4f-5a6b-7c8d9e0f1a2b",
"chatType": "PRIVATE",
"msgType": "Text",
"senderId": "8f3a2c10-1b2c-4d5e-9a8b-7c6d5e4f3a2b",
"targetId": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"state": "Sent",
"streamKey": "stream:private:xxx",
"sequenceId": 1021,
"creationTime": "2026-06-26T18:35:00+00:00",
"content": {
"fallback": "晚上一起吃饭吗?",
"body": { "text": "晚上一起吃饭吗?" },
"ext": {},
"quote": null
}
}
}
```
**POST /api/Message/WithDraw** — 撤回消息
参数:`msgId` (query)
响应示例:
```json
{ "code": 0, "message": "成功", "data": true }
```
**GET /api/Message/GetMessages** — 拉取消息
| 参数 | 类型 | 说明 |
|------|------|------|
| conversationId | guid | 会话 ID |
| cursor | long? | 游标 |
| direction | int | 方向 |
| limit | int | 数量 |
请求示例:
```
GET /api/Message/GetMessages?conversationId=d4e5f6a7-b8c9-0d1e-2f3a-4b5c6d7e8f9a&cursor=1021&direction=0&limit=20
```
响应示例:
```json
{
"code": 0,
"message": "成功",
"data": {
"messages": [
{
"id": "9a0b1c2d-3e4f-5a6b-7c8d-9e0f1a2b3c4d",
"clientMsgId": "7e8f9a0b-1c2d-3e4f-5a6b-7c8d9e0f1a2b",
"chatType": "PRIVATE",
"msgType": 0,
"senderId": "8f3a2c10-1b2c-4d5e-9a8b-7c6d5e4f3a2b",
"targetId": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"state": "Sent",
"streamKey": "stream:private:xxx",
"sequenceId": 1021,
"creationTime": "2026-06-26T18:35:00+00:00",
"content": { "fallback": "晚上一起吃饭吗?", "body": { "text": "晚上一起吃饭吗?" }, "ext": {}, "quote": null }
}
],
"hasmore": true
}
}
```
**消息类型 (MsgType)**`"Text"` 文本, `"Image"` 图片, `"Voice"` 语音, `"Video"` 视频, `"File"` 文件, `"VoiceChat"` 语音通话, `"VideoChat"` 视频通话
**会话类型 (ChatType)**`"PRIVATE"` 单聊, `"GROUP"` 群聊
---
## 6. 文件服务 (FileService.WebApi)
> 需认证。路由为 `api/File` 与 `api/FileTask`。
### 文件 (File) — 小文件直传/下载
**POST /api/File/simple-upload** — 单次直传(头像/封面等),**支持秒传**
- Content-Type: `multipart/form-data`
- 表单字段:`file` (文件), `isPublic` (booltrue 落公开桶返回直链)
- 秒传:服务端计算文件 MD5 后,若已存在相同 checksum 的文件则直接返回已有记录,**跳过存储写入**
- 响应:`Result<FileResponse>`
请求示例(form-data):
```
file: (二进制文件)
isPublic: true
```
秒传命中响应(与正常上传返回一致,但实际未写入存储):
```json
{
"code": 0,
"message": "成功",
"data": {
"id": "a0b1c2d3-e4f5-6a7b-8c9d-0e1f2a3b4c5d",
"ownerId": "8f3a2c10-1b2c-4d5e-9a8b-7c6d5e4f3a2b",
"fileName": "avatar.png",
"fileSize": 20480,
"contentType": "image/png",
"state": "Uploaded",
"storageLocation": {
"storageProvider": "local",
"bucket": "im-public",
"objectKey": "2026/06/26/abc123.png",
"region": "local"
},
"checkSum": "d41d8cd98f00b204e9800998ecf8427e",
"created": "2026-06-26T09:00:00+00:00",
"updated": "2026-06-26T09:00:00+00:00",
"url": "https://cdn.example.com/public/avatar.png"
}
}
```
正常上传响应示例:
```json
{
"code": 0,
"message": "成功",
"data": {
"id": "a0b1c2d3-e4f5-6a7b-8c9d-0e1f2a3b4c5d",
"ownerId": "8f3a2c10-1b2c-4d5e-9a8b-7c6d5e4f3a2b",
"fileName": "avatar.png",
"fileSize": 20480,
"contentType": "image/png",
"state": "Uploaded",
"storageLocation": {
"storageProvider": "local",
"bucket": "im-public",
"objectKey": "2026/06/26/abc123.png",
"region": "local"
},
"checkSum": "d41d8cd98f00b204e9800998ecf8427e",
"created": "2026-06-26T09:00:00+00:00",
"updated": "2026-06-26T09:00:00+00:00",
"url": "https://cdn.example.com/public/avatar.png"
}
}
```
**GET /api/File/{id}** — 获取文件信息(含直链 Url,私有文件 Url 为 null
响应:`Result<FileResponse>`(结构同上;私有文件 `url``null`
**GET /api/File/{id}/content** — 鉴权下载文件内容(私有文件预览/下载)
响应:文件流 (`File` 结果,二进制;非 `Result<T>` 包装)
### 分片上传任务 (FileTask) — 大文件分片
> 完整流程:Init → GetUploadUrl → UploadPart → Complete(四步)
> 支持秒传、分片大小校验、上传进度查询。
**POST /api/FileTask/init** — 初始化上传任务(支持秒传)
| 字段 | 类型 | 说明 |
|------|------|------|
| conversationId | guid | 会话 ID |
| fileName | string | 文件名 |
| fileSize | long | 文件大小(≤ 1GB,由 `MaxObjectSizeBytes` 控制) |
| contentType | string | MIME 类型 |
| checkSum | string | 校验和(必须,用于秒传去重) |
请求示例:
```json
{
"conversationId": "d4e5f6a7-b8c9-0d1e-2f3a-4b5c6d7e8f9a",
"fileName": "movie.mp4",
"fileSize": 104857600,
"contentType": "video/mp4",
"checkSum": "9e107d9d372bb6826bd81d3542a419d6"
}
```
响应示例:
```json
{
"code": 0,
"message": "成功",
"data": {
"taskId": "b1c2d3e4-f5a6-7b8c-9d0e-1f2a3b4c5d6e",
"uploadSessionId": "sess_abc123",
"storageLocation": {
"storageProvider": "local",
"bucket": "im-local",
"objectKey": "data/im-files/2026/06/27/movie.mp4",
"region": "local"
}
}
}
```
秒传命中(checksum 匹配已完成的文件,跳过初始化):
```json
{
"code": 0,
"message": "成功",
"data": {
"taskId": "a0b1c2d3-e4f5-6a7b-8c9d-0e1f2a3b4c5d",
"uploadSessionId": "a0b1c2d3-e4f5-6a7b-8c9d-0e1f2a3b4c5d",
"storageLocation": {
"storageProvider": "local",
"bucket": "im-local",
"objectKey": "2026/06/26/existing.png",
"region": "local"
}
}
}
```
此时 `taskId` 即为已存在文件的 ID,前端可直接跳到 Complete。
文件过大失败:
```json
{ "code": 2402, "message": "文件大小超限", "data": null }
```
**GET /api/FileTask/Getuploadurl** — 获取分片上传地址
参数:`sessionId`, `partNum` (query)
- `partNum` 必须满足 `1 ≤ partNum ≤ totalPartCount`,越界返回 `3206`(无效分片号)
请求示例:
```
GET /api/FileTask/Getuploadurl?sessionId=sess_abc123&partNum=1
```
响应示例:
```json
{
"code": 0,
"message": "成功",
"data": "https://oss.example.com/upload?sessionId=sess_abc123&partNum=1&signature=xxx"
}
```
**GET /api/FileTask/progress****查询上传进度(新增)**
参数:`sessionId` (query)
请求示例:
```
GET /api/FileTask/progress?sessionId=sess_abc123
```
响应示例:
```json
{
"code": 0,
"message": "成功",
"data": {
"sessionId": "sess_abc123",
"taskId": "b1c2d3e4-f5a6-7b8c-9d0e-1f2a3b4c5d6e",
"fileSize": 104857600,
"totalPartCount": 20,
"completedPartCount": 7,
"uploadedBytes": 36700160,
"progressPercent": 35
}
}
```
**POST /api/FileTask/complete** — 完成上传(合并分片)
| 字段 | 类型 | 说明 |
|------|------|------|
| sessionId | string | 会话 ID |
| parts | UploadPart[] | 分片列表(**必须与 `totalPartCount` 数量一致** |
分片数量不匹配返回 `3204`(分片数量不匹配)。
请求示例:
```json
{
"sessionId": "sess_abc123",
"parts": [
{ "partNumber": 1, "eTag": "etag-part-1", "size": 5242880 },
{ "partNumber": 2, "eTag": "etag-part-2", "size": 5242880 }
]
}
```
响应示例:`Result<FileResponse>`(结构同 simple-upload
**POST /api/FileTask/local/parts/upload** — 本地分片上传(支持分片大小校验)
表单字段:`sessionId`, `partNumber`, `file`
- 非末片大小 ≥ `MinPartSizeBytes`(默认 5MB),否则返回 `3203`(分片过小)
- 末片豁免最小值校验
请求示例(form-data):
```
sessionId: sess_abc123
partNumber: 1
file: (二进制分片)
```
---
## 附录:业务状态码表
| 范围 | 分类 | 示例 |
|------|------|------|
| 0 | 成功 | 0 成功 |
| 1000-1999 | 系统级 | 1003 参数错误, 1005 权限不足, 1006 认证失败 |
| 2000-2099 | 用户 | 2000 用户不存在, 2001 用户已存在, 2002 密码错误 |
| 2100-2199 | 好友 | 2100 好友申请已存在, 2102 已经是好友 |
| 2200-2299 | 群聊 | 2200 群不存在, 2201 已在群中, 2202 群成员已满 |
| 2300-2399 | 消息 | 2300 发送失败, 2301 消息不存在, 2302 撤回失败 |
| 2400-2499 | 文件 | 2400 上传失败, 2401 文件不存在, 2402 大小超限 |
| 3000-3099 | 管理后台 | 3000 管理员不存在, 3003 权限不足 |
| 3100-3199 | 会话 | 3100 会话不存在 |
| 3200-3299 | 分片 | 3201 分片不存在, 3202 分片合并失败, **3203 分片过小**, **3204 分片数不匹配**, **3205 会话过期**, **3206 分片号无效** |