# IM_API_NEW 接口文档 基于微服务架构的即时通讯系统,包含 6 个独立 WebApi 服务。 ## 通用说明 ### 统一响应格式 `Result` ```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` 请求示例: ```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` 请求示例: ```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` 请求示例: ```json { "refreshToken": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d" } ``` 响应示例:同 Login 的 `Result`。 **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` 响应示例: ```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`(结构同上) ### GET /api/User/FindByUname?username={string} — 按用户名查询 响应:`Result`(结构同上) ### POST /api/User/Update — 更新资料 请求体(均可选):`nickName`、`region`、`avatar`、`description` 响应:`Result` 请求示例: ```json { "nickName": "张三丰", "region": "湖北·武当山", "avatar": "https://cdn.example.com/avatar/new.png", "description": "太极宗师" } ``` ### POST /api/User/GetUsersByIds — 批量查询用户 请求体:`["guid", "guid"]`(Guid 数组) 响应:`Result>` 请求示例: ```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` (bool,true 落公开桶返回直链) - 秒传:服务端计算文件 MD5 后,若已存在相同 checksum 的文件则直接返回已有记录,**跳过存储写入** - 响应:`Result` 请求示例(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`(结构同上;私有文件 `url` 为 `null`) **GET /api/File/{id}/content** — 鉴权下载文件内容(私有文件预览/下载) 响应:文件流 (`File` 结果,二进制;非 `Result` 包装) ### 分片上传任务 (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`(结构同 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 分片号无效** | ## 会话一致性说明 - `GET /api/Conversation/List` 的请求和响应结构不变,结果按 `ModificationTime ?? CreationTime` 倒序返回。 - 活动会话由 `(UserId, ChatType, TargetId)` 唯一确定;重复的好友或入群事件按幂等成功处理。 - `20260911000100_ConversationUniqueness` 会保留最新活动记录、合并最大未读数和最大已读序号,并软删除其余重复记录。 # 消息历史搜索 `GET /api/Message/Search` 在当前用户拥有的会话中搜索历史文本消息。 - 参数:`conversationId`、`keyword`(去除首尾空格后 1–50 字符)、可选独占游标 `cursor`、`limit`(1–50,默认 30)。 - 只返回未撤回、未删除的文本消息,按 `sequenceId` 倒序排列。 - 响应继续使用统一 `Result`,数据结构为 `{ messages, hasmore }`。下一页以本页最后一条消息的 `sequenceId` 作为独占游标。 - `POST /api/Conversation/MarkRead` 新增可选 `lastReadSequenceId` 查询参数,旧客户端不传时仍兼容。 - 会话列表的 `dateTime` 表示最后消息活动时间;标记已读不会改变此时间或会话排序。