26 KiB
IM_API_NEW 接口文档
基于微服务架构的即时通讯系统,包含 6 个独立 WebApi 服务。
通用说明
统一响应格式 Result<T>
{
"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>
请求示例:
{
"userName": "zhangsan",
"password": "P@ssw0rd"
}
响应示例:
{
"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):
{
"code": 2002,
"message": "密码错误",
"data": null
}
POST /api/Auth/Register — 注册
请求体:
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| userName | string | 是 | 5-20 字符 |
| password | string | 是 | 6-50 字符 |
| nickName | string | 是 | ≤50 字符 |
响应:Result<UserResponse>
请求示例:
{
"userName": "zhangsan",
"password": "P@ssw0rd",
"nickName": "张三"
}
响应示例:
{
"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>
请求示例:
{
"refreshToken": "9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d"
}
响应示例:同 Login 的 Result<LoginResponse>。
LoginResponse 结构
{
"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>
响应示例:
{
"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>
请求示例:
{
"nickName": "张三丰",
"region": "湖北·武当山",
"avatar": "https://cdn.example.com/avatar/new.png",
"description": "太极宗师"
}
POST /api/User/GetUsersByIds — 批量查询用户
请求体:["guid", "guid"](Guid 数组)
响应:Result<List<UserResponse>>
请求示例:
[
"8f3a2c10-1b2c-4d5e-9a8b-7c6d5e4f3a2b",
"1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
]
响应示例:
{
"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 结构
{
"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 响应示例:
{
"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} 响应示例:
{ "code": 0, "message": "成功", "data": true }
GET /api/Friend/CheckFriend?userId={guid}&targetId={guid} 响应示例:
{ "code": 0, "message": "成功", "data": true }
好友请求 (FriendRequest)
POST /api/FriendRequest/Add — 发起好友申请
| 字段 | 类型 | 必填 | 说明 |
|---|---|---|---|
| targetId | guid | 是 | 被申请人 |
| description | string | 否 | 附言 |
| remarkName | string | 否 | 备注名 |
请求示例:
{
"targetId": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"description": "我是张三,加个好友",
"remarkName": "老李"
}
响应示例:
{
"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 | 同意时必填 | 备注名 |
请求示例:
{
"requestId": "c1d2e3f4-a5b6-7c8d-9e0f-1a2b3c4d5e6f",
"action": "Accpet",
"remarkName": "张三"
}
响应示例:
{ "code": 0, "message": "成功", "data": true }
GET /api/FriendRequest/List — 我相关的申请列表
响应示例:
{
"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 请求示例:
{ "name": "技术交流群" }
POST /api/Group/Update 请求示例:
{
"groupId": "a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d",
"groupName": "技术交流群(2026)",
"avatar": "https://cdn.example.com/group/tech.png",
"description": "欢迎交流技术"
}
GET /api/Group/GetOne?groupId={guid} 响应示例:
{
"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} 响应示例:
{
"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 请求示例:
{
"groupId": "a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d",
"userId": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d"
}
GET /api/GroupInvitation/Get?invitationId={guid} 响应示例:
{
"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 请求示例:
{
"groupId": "a1b2c3d4-e5f6-7a8b-9c0d-1e2f3a4b5c6d",
"desc": "我想加入学习"
}
GET /api/GroupRequest/Find?id={guid} 响应示例:
{
"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 响应示例:
{
"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} 响应示例:
{ "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 | 否 | 时长(语音/视频) |
文本消息请求示例:
{
"clientMsgId": "7e8f9a0b-1c2d-3e4f-5a6b-7c8d9e0f1a2b",
"targetId": "1a2b3c4d-5e6f-7a8b-9c0d-1e2f3a4b5c6d",
"chatType": "PRIVATE",
"msgType": "Text",
"text": "晚上一起吃饭吗?"
}
图片消息请求示例:
{
"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" }
}
发送响应示例:
{
"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)
响应示例:
{ "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
响应示例:
{
"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<FileResponse>
请求示例(form-data):
file: (二进制文件)
isPublic: true
秒传命中响应(与正常上传返回一致,但实际未写入存储):
{
"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"
}
}
正常上传响应示例:
{
"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 | 校验和(必须,用于秒传去重) |
请求示例:
{
"conversationId": "d4e5f6a7-b8c9-0d1e-2f3a-4b5c6d7e8f9a",
"fileName": "movie.mp4",
"fileSize": 104857600,
"contentType": "video/mp4",
"checkSum": "9e107d9d372bb6826bd81d3542a419d6"
}
响应示例:
{
"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 匹配已完成的文件,跳过初始化):
{
"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。
文件过大失败:
{ "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
响应示例:
{
"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
响应示例:
{
"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(分片数量不匹配)。
请求示例:
{
"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 分片号无效 |
会话一致性说明
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表示最后消息活动时间;标记已读不会改变此时间或会话排序。