Files
IM_NEW/API.md
T

948 lines
25 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_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 分片号无效** |