{
  "openapi": "3.0.3",
  "info": {
    "title": "flow-talk-server 接口",
    "description": "当前服务已实现用户认证、会话、消息、WebSocket 实时通信、设备、群管理、消息回执、在线状态和消息搜索接口。",
    "version": "1.0.0"
  },
  "servers": [
    {
      "url": "http://127.0.0.1:8080",
      "description": "本地开发环境"
    }
  ],
  "tags": [
    {
      "name": "认证",
      "description": "注册、登录、当前用户"
    },
    {
      "name": "用户",
      "description": "用户信息接口"
    },
    {
      "name": "会话",
      "description": "单聊、群聊和会话成员接口"
    },
    {
      "name": "消息",
      "description": "消息发送、历史消息、软撤回和已读接口"
    },
    {
      "name": "WebSocket",
      "description": "实时连接、心跳和消息投递协议"
    },
    {
      "name": "设备",
      "description": "用户设备上报、查询和删除"
    },
    {
      "name": "群管理",
      "description": "群资料、群成员和角色管理"
    },
    {
      "name": "消息回执",
      "description": "逐条消息已读和未读回执"
    },
    {
      "name": "在线状态",
      "description": "在线状态查询；启用 Redis 后返回跨实例全局在线状态"
    },
    {
      "name": "消息搜索",
      "description": "文本消息搜索"
    },
    {
      "name": "资源",
      "description": "图片、视频等静态资源上传"
    }
  ],
  "paths": {
    "/api/auth/register": {
      "post": {
        "tags": [
          "认证"
        ],
        "summary": "注册",
        "description": "创建本地用户，注册成功后直接返回用户信息和登录 token。",
        "operationId": "register",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RegisterRequest"
              },
              "example": {
                "username": "alice",
                "password": "123456",
                "nickname": "Alice",
                "avatar_base64": "data:image/png;base64,iVBORw0KGgo="
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "注册成功",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthApiResponse"
                },
                "example": {
                  "code": 200,
                  "data": {
                    "token": "<jwt-token>",
                    "user": {
                      "id": 1,
                      "username": "alice",
                      "nickname": "Alice",
                      "avatar_url": "https://example.com/avatar.png",
                      "auth_source": "local",
                      "status": 1
                    }
                  },
                  "message": "注册成功"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "413": {
            "$ref": "#/components/responses/AvatarTooLarge"
          },
          "409": {
            "$ref": "#/components/responses/UsernameTaken"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/auth/login": {
      "post": {
        "tags": [
          "认证"
        ],
        "summary": "登录",
        "description": "使用本地账号密码登录，成功后返回用户信息和新的 token。",
        "operationId": "login",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/LoginRequest"
              },
              "example": {
                "username": "alice",
                "password": "123456"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "登录成功",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthApiResponse"
                },
                "example": {
                  "code": 200,
                  "data": {
                    "token": "<jwt-token>",
                    "user": {
                      "id": 1,
                      "username": "alice",
                      "nickname": "Alice",
                      "avatar_url": "https://example.com/avatar.png",
                      "auth_source": "local",
                      "status": 1
                    }
                  },
                  "message": "登录成功"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/InvalidCredentials"
          },
          "403": {
            "$ref": "#/components/responses/UserDisabled"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/me": {
      "get": {
        "tags": [
          "认证"
        ],
        "summary": "获取当前用户",
        "description": "根据请求头中的 token 返回当前登录用户信息。",
        "operationId": "getCurrentUser",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "获取成功",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UserApiResponse"
                },
                "example": {
                  "code": 200,
                  "data": {
                    "id": 1,
                    "username": "alice",
                    "nickname": "Alice",
                    "avatar_url": "https://example.com/avatar.png",
                    "auth_source": "local",
                    "status": 1
                  },
                  "message": "获取当前用户成功"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/api/conversations": {
      "get": {
        "tags": [
          "会话"
        ],
        "summary": "获取会话列表",
        "description": "返回当前用户参与且状态为 active 的会话列表。",
        "operationId": "listConversations",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "获取成功",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConversationListApiResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/conversations/direct": {
      "post": {
        "tags": [
          "会话"
        ],
        "summary": "创建或获取单聊",
        "description": "根据目标用户创建或获取单聊会话，服务端使用 direct_key 防止重复创建。",
        "operationId": "createDirectConversation",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateDirectConversationRequest"
              },
              "example": {
                "target_user_id": 2
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "获取成功",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConversationDetailApiResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/conversations/groups": {
      "post": {
        "tags": [
          "会话"
        ],
        "summary": "创建群聊",
        "description": "创建群聊并写入群成员，当前用户自动成为群主。",
        "operationId": "createGroupConversation",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateGroupConversationRequest"
              },
              "example": {
                "title": "测试群",
                "avatar_url": "",
                "member_ids": [
                  2,
                  3
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "创建成功",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConversationApiResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/users": {
      "get": {
        "tags": [
          "用户"
        ],
        "summary": "获取用户列表",
        "description": "返回用户列表。传入 all=true 时读取全部用户；不传 all 时支持 limit/offset 分页。",
        "operationId": "listUsers",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "parameters": [
          {
            "name": "all",
            "in": "query",
            "description": "是否读取全部用户",
            "schema": {
              "type": "boolean",
              "example": true
            }
          },
          {
            "name": "limit",
            "in": "query",
            "description": "分页条数，不传默认 50，最大 500",
            "schema": {
              "type": "integer",
              "example": 50
            }
          },
          {
            "name": "offset",
            "in": "query",
            "description": "分页偏移",
            "schema": {
              "type": "integer",
              "example": 0
            }
          },
          {
            "name": "keyword",
            "in": "query",
            "description": "搜索用户名、昵称或外部 ID",
            "schema": {
              "type": "string",
              "example": "alice"
            }
          },
          {
            "name": "status",
            "in": "query",
            "description": "用户状态，1 正常，0 禁用",
            "schema": {
              "type": "integer",
              "example": 1
            }
          },
          {
            "name": "auth_source",
            "in": "query",
            "description": "用户来源，local 或 external",
            "schema": {
              "type": "string",
              "example": "external"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "获取成功",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/UserListApiResponse"
                },
                "example": {
                  "code": 200,
                  "data": [
                    {
                      "id": 2,
                      "username": "bob",
                      "nickname": "Bob",
                      "avatar_url": "",
                      "auth_source": "local",
                      "status": 1
                    },
                    {
                      "id": 1,
                      "username": "alice",
                      "nickname": "Alice",
                      "avatar_url": "https://example.com/avatar.png",
                      "auth_source": "local",
                      "status": 1
                    }
                  ],
                  "message": "获取用户列表成功"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/ws": {
      "get": {
        "tags": [
          "WebSocket"
        ],
        "summary": "建立 WebSocket 连接",
        "description": "地址格式：GET /api/ws?token={jwt}&device_id={device_id}。事件格式统一为 {type, request_id, payload}，支持 ping、message.send、message.ack、message.deliver、presence.changed、conversation.unread.changed、conversation.changed、error。客户端每 25-30 秒发送 ping，服务端 75 秒没有收到事件会回收连接。聊天页面发送消息应优先使用 message.send，不要同时调用 HTTP 发送接口。收到 conversation.unread.changed 后必须用服务端 unread_count 替换本地数量，不要自行 +1/-1；收到 conversation.changed 后重新获取会话列表，并在目标会话已打开时重新获取详情。登录初始化和 WebSocket 重连后应重新获取会话与 presence HTTP 快照。",
        "parameters": [
          {
            "name": "token",
            "in": "query",
            "required": true,
            "schema": {
              "type": "string"
            },
            "description": "JWT token"
          },
          {
            "name": "device_id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "设备 ID"
          }
        ],
        "responses": {
          "101": {
            "description": "协议升级成功"
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/auth/external": {
      "post": {
        "tags": [
          "认证"
        ],
        "summary": "外部登录",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ExternalLoginRequest"
              },
              "example": {
                "provider": "demo",
                "access_token": "external-user-001"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "登录成功",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "code": {
                      "type": "integer",
                      "example": 200
                    },
                    "data": {
                      "$ref": "#/components/schemas/AuthApiResponse"
                    },
                    "message": {
                      "type": "string",
                      "example": "登录成功"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/devices": {
      "post": {
        "tags": [
          "设备"
        ],
        "summary": "上报设备",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpsertDeviceRequest"
              },
              "example": {
                "user_id": 1,
                "data": {
                  "platform": "web",
                  "device_id": "web-001",
                  "app_version": "1.0.0"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "上报设备成功",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "code": {
                      "type": "integer",
                      "example": 200
                    },
                    "data": {
                      "$ref": "#/components/schemas/Device"
                    },
                    "message": {
                      "type": "string",
                      "example": "上报设备成功"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "get": {
        "tags": [
          "设备"
        ],
        "summary": "获取设备列表",
        "responses": {
          "200": {
            "description": "获取设备列表成功",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "code": {
                      "type": "integer",
                      "example": 200
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Device"
                      }
                    },
                    "message": {
                      "type": "string",
                      "example": "获取设备列表成功"
                    }
                  }
                }
              }
            }
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      },
      "delete": {
        "tags": [
          "设备"
        ],
        "summary": "删除当前用户设备上报数据",
        "responses": {
          "200": {
            "description": "删除设备成功",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "code": {
                      "type": "integer",
                      "example": 200
                    },
                    "data": {
                      "nullable": true,
                      "example": null
                    },
                    "message": {
                      "type": "string",
                      "example": "删除设备成功"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/messages/search": {
      "post": {
        "tags": [
          "消息搜索"
        ],
        "summary": "搜索我的消息",
        "responses": {
          "200": {
            "description": "搜索消息成功",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "code": {
                      "type": "integer",
                      "example": 200
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Message"
                      }
                    },
                    "message": {
                      "type": "string",
                      "example": "搜索消息成功"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SearchMyMessagesRequest"
              },
              "example": {
                "keyword": "hello",
                "limit": 20
              }
            }
          }
        },
        "description": "通过请求体传入关键词，在当前用户可访问的消息中搜索。",
        "operationId": "searchMyMessages"
      }
    },
    "/api/users/presence/batch": {
      "post": {
        "tags": [
          "在线状态"
        ],
        "summary": "获取在线人员列表",
        "operationId": "listUserPresence",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/BatchPresenceRequest"
              },
              "examples": {
                "all": {
                  "summary": "读取所有用户",
                  "value": {
                    "type": "all"
                  }
                },
                "online": {
                  "summary": "查询在线人员",
                  "value": {
                    "type": "online"
                  }
                },
                "specified": {
                  "summary": "查询指定人员",
                  "value": {
                    "type": "specified",
                    "user_ids": [
                      1,
                      2
                    ]
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "获取在线人员成功",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "code": {
                      "type": "integer",
                      "example": 200
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/UserPresence"
                      }
                    },
                    "message": {
                      "type": "string",
                      "example": "获取在线人员成功"
                    }
                  }
                },
                "example": {
                  "code": 200,
                  "data": [
                    {
                      "id": 1,
                      "user_id": 1,
                      "username": "alice",
                      "nickname": "Alice",
                      "avatar_url": "https://example.com/avatar.png",
                      "auth_source": "local",
                      "status": 1,
                      "online": true,
                      "connection_count": 1,
                      "last_active_at": "2026-06-29T12:00:00+08:00"
                    },
                    {
                      "id": 2,
                      "user_id": 2,
                      "username": "bob",
                      "nickname": "Bob",
                      "auth_source": "local",
                      "status": 1,
                      "online": false,
                      "connection_count": 0,
                      "last_seen_at": "2026-06-29T11:00:00+08:00"
                    }
                  ],
                  "message": "获取在线人员成功"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "description": "返回用户资料和在线状态，前端可直接渲染人员列表。通过 type 选择人员范围：all 全部、online 在线、offline 离线、specified 指定人员。specified 需要同时传入 user_ids；兼容旧客户端只传 user_ids 的请求。"
      }
    },
    "/api/resources/upload": {
      "post": {
        "tags": [
          "资源"
        ],
        "summary": "上传图片或视频资源",
        "description": "需要登录。图片保存到 static/images/{user_id}/{timestamp}.{ext}，视频保存到 static/videos/{user_id}/{timestamp}.{ext}。",
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "type": "object",
                "required": [
                  "file",
                  "type"
                ],
                "properties": {
                  "file": {
                    "type": "string",
                    "format": "binary",
                    "description": "上传文件"
                  },
                  "type": {
                    "type": "string",
                    "enum": [
                      "image",
                      "video"
                    ],
                    "description": "资源类型"
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "上传资源成功",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "code": {
                      "type": "integer",
                      "example": 200
                    },
                    "data": {
                      "$ref": "#/components/schemas/Resource"
                    },
                    "message": {
                      "type": "string",
                      "example": "上传资源成功"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/conversations/detail": {
      "post": {
        "tags": [
          "会话"
        ],
        "summary": "获取会话详情",
        "description": "通过请求体传入会话 ID，返回单个会话详情和成员列表。",
        "operationId": "getConversationDetail",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "获取成功",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ConversationDetailApiResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ConversationIDRequest"
              },
              "example": {
                "conversation_id": 1
              }
            }
          }
        }
      }
    },
    "/api/conversations/profile": {
      "patch": {
        "tags": [
          "群管理"
        ],
        "summary": "修改群资料",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateGroupProfileRequest"
              },
              "example": {
                "conversation_id": 1,
                "title": "新的群名称",
                "avatar_url": "https://example.com/group.png"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "修改群资料成功",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "code": {
                      "type": "integer",
                      "example": 200
                    },
                    "data": {
                      "$ref": "#/components/schemas/Conversation"
                    },
                    "message": {
                      "type": "string",
                      "example": "修改群资料成功"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "description": "通过请求体传入会话 ID 和群资料字段，仅群主或管理员可操作。",
        "operationId": "updateGroupProfile"
      }
    },
    "/api/conversations/members": {
      "post": {
        "tags": [
          "群管理"
        ],
        "summary": "邀请成员加入群聊",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AddMembersRequest"
              },
              "example": {
                "conversation_id": 1,
                "user_ids": [
                  2,
                  3
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "添加群成员成功",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "code": {
                      "type": "integer",
                      "example": 200
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/ConversationMember"
                      }
                    },
                    "message": {
                      "type": "string",
                      "example": "添加群成员成功"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "description": "通过请求体传入会话 ID 和用户 ID 列表。",
        "operationId": "addGroupMembers"
      },
      "delete": {
        "tags": [
          "群管理"
        ],
        "summary": "移除群成员",
        "responses": {
          "200": {
            "description": "移除群成员成功",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "code": {
                      "type": "integer",
                      "example": 200
                    },
                    "data": {
                      "nullable": true,
                      "example": null
                    },
                    "message": {
                      "type": "string",
                      "example": "移除群成员成功"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RemoveMemberRequest"
              },
              "example": {
                "conversation_id": 1,
                "user_id": 2
              }
            }
          }
        },
        "description": "通过请求体传入会话 ID 和待移除用户 ID。",
        "operationId": "removeGroupMember"
      }
    },
    "/api/conversations/leave": {
      "post": {
        "tags": [
          "群管理"
        ],
        "summary": "退出群聊",
        "responses": {
          "200": {
            "description": "退出群聊成功",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "code": {
                      "type": "integer",
                      "example": 200
                    },
                    "data": {
                      "nullable": true,
                      "example": null
                    },
                    "message": {
                      "type": "string",
                      "example": "退出群聊成功"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ConversationIDRequest"
              },
              "example": {
                "conversation_id": 1
              }
            }
          }
        },
        "description": "通过请求体传入会话 ID，当前用户退出群聊。",
        "operationId": "leaveGroup"
      }
    },
    "/api/conversations/members/role": {
      "patch": {
        "tags": [
          "群管理"
        ],
        "summary": "修改群成员角色",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/UpdateMemberRoleRequest"
              },
              "example": {
                "conversation_id": 1,
                "user_id": 2,
                "role": "admin"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "更新成员角色成功",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "code": {
                      "type": "integer",
                      "example": 200
                    },
                    "data": {
                      "$ref": "#/components/schemas/ConversationMember"
                    },
                    "message": {
                      "type": "string",
                      "example": "更新成员角色成功"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "description": "通过请求体传入会话 ID、成员用户 ID 和新角色。",
        "operationId": "updateMemberRole"
      }
    },
    "/api/conversations/messages": {
      "post": {
        "tags": [
          "消息"
        ],
        "summary": "发送消息（HTTP 降级接口）",
        "description": "通过请求体传入会话 ID 和消息内容。聊天页面首选 WebSocket message.send；HTTP 接口用于调试、降级发送和离线补偿。客户端降级时应复用同一个 client_msg_id，避免重复消息。",
        "operationId": "sendMessage",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SendMessageRequest"
              },
              "example": {
                "conversation_id": 1,
                "client_msg_id": "client-001",
                "message_type": "text",
                "content": {
                  "text": "hello"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "发送成功",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MessageApiResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/conversations/messages/list": {
      "post": {
        "tags": [
          "消息"
        ],
        "summary": "获取历史消息",
        "description": "通过请求体传入会话 ID 和分页参数，分页拉取指定会话的历史消息。",
        "operationId": "listMessages",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "获取成功",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/MessagePageApiResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ListMessagesRequest"
              },
              "example": {
                "conversation_id": 1,
                "before_id": 1001,
                "limit": 20
              }
            }
          }
        }
      }
    },
    "/api/conversations/messages/search": {
      "post": {
        "tags": [
          "消息搜索"
        ],
        "summary": "搜索指定会话消息",
        "responses": {
          "200": {
            "description": "搜索消息成功",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "code": {
                      "type": "integer",
                      "example": 200
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/Message"
                      }
                    },
                    "message": {
                      "type": "string",
                      "example": "搜索消息成功"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/SearchConversationMessagesRequest"
              },
              "example": {
                "conversation_id": 1,
                "keyword": "hello",
                "limit": 20
              }
            }
          }
        },
        "description": "通过请求体传入会话 ID 和关键词，仅在指定会话内搜索消息。",
        "operationId": "searchConversationMessages"
      }
    },
    "/api/conversations/read": {
      "post": {
        "tags": [
          "消息"
        ],
        "summary": "更新会话已读位置",
        "description": "通过请求体传入会话 ID 和最后已读消息 ID，用于更新当前用户在会话内的已读位置。响应包含权威未读数，同时向当前用户全部在线连接推送 conversation.unread.changed。",
        "operationId": "markConversationRead",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MarkReadRequest"
              },
              "example": {
                "conversation_id": 1,
                "last_read_message_id": 1001
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "标记成功",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReadStateApiResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/messages/recall": {
      "post": {
        "tags": [
          "消息"
        ],
        "summary": "撤回消息",
        "description": "通过请求体传入消息 ID，软撤回当前用户本人发送的正常消息。服务端不会删除 messages.content，只把消息状态更新为 recalled；历史消息、搜索、未读统计和会话最后消息只展示 normal 消息。撤回成功后服务端向会话 active 成员推送 conversation.changed，change_type 为 message。",
        "operationId": "recallMessage",
        "security": [
          {
            "bearerAuth": []
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/RecallMessageRequest"
              },
              "example": {
                "message_id": 1001
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "消息已撤回",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/NullApiResponse"
                },
                "example": {
                  "code": 200,
                  "data": null,
                  "message": "消息已撤回"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        }
      }
    },
    "/api/messages/receipts": {
      "post": {
        "tags": [
          "消息回执"
        ],
        "summary": "获取消息回执",
        "responses": {
          "200": {
            "description": "获取消息回执成功",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "code": {
                      "type": "integer",
                      "example": 200
                    },
                    "data": {
                      "type": "array",
                      "items": {
                        "$ref": "#/components/schemas/MessageReceipt"
                      }
                    },
                    "message": {
                      "type": "string",
                      "example": "获取消息回执成功"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MessageReceiptRequest"
              },
              "example": {
                "message_id": 1001
              }
            }
          }
        },
        "description": "通过请求体传入消息 ID，获取该消息的成员已读/未读回执。",
        "operationId": "listMessageReceipts"
      }
    },
    "/api/messages/read": {
      "post": {
        "tags": [
          "消息回执"
        ],
        "summary": "标记消息已读",
        "responses": {
          "200": {
            "description": "标记已读成功",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "code": {
                      "type": "integer",
                      "example": 200
                    },
                    "data": {
                      "nullable": true,
                      "example": null
                    },
                    "message": {
                      "type": "string",
                      "example": "标记已读成功"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MessageReceiptRequest"
              },
              "example": {
                "message_id": 1001
              }
            }
          }
        },
        "description": "通过请求体传入消息 ID，将当前用户对该消息的回执标记为 read。",
        "operationId": "markMessageRead"
      }
    },
    "/api/messages/unread": {
      "post": {
        "tags": [
          "消息回执"
        ],
        "summary": "标记消息未读",
        "responses": {
          "200": {
            "description": "标记未读成功",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "code": {
                      "type": "integer",
                      "example": 200
                    },
                    "data": {
                      "nullable": true,
                      "example": null
                    },
                    "message": {
                      "type": "string",
                      "example": "标记未读成功"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/MessageReceiptRequest"
              },
              "example": {
                "message_id": 1001
              }
            }
          }
        },
        "description": "通过请求体传入消息 ID，将当前用户对该消息的回执标记为 unread。",
        "operationId": "markMessageUnread"
      }
    },
    "/api/users/presence": {
      "post": {
        "tags": [
          "在线状态"
        ],
        "summary": "获取用户在线状态",
        "responses": {
          "200": {
            "description": "获取在线状态成功",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "code": {
                      "type": "integer",
                      "example": 200
                    },
                    "data": {
                      "$ref": "#/components/schemas/Presence"
                    },
                    "message": {
                      "type": "string",
                      "example": "获取在线状态成功"
                    }
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "500": {
            "$ref": "#/components/responses/InternalError"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PresenceRequest"
              },
              "example": {
                "user_id": 2
              }
            }
          }
        },
        "description": "通过请求体传入用户 ID，获取单个用户在线状态。",
        "operationId": "getUserPresence"
      }
    }
  },
  "components": {
    "securitySchemes": {
      "bearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "description": "登录或注册接口返回的 token。"
      }
    },
    "responses": {
      "ValidationError": {
        "description": "参数校验失败",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorApiResponse"
            },
            "example": {
              "code": 400,
              "data": null,
              "message": "参数校验失败"
            }
          }
        }
      },
      "Unauthorized": {
        "description": "未登录或登录已失效",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorApiResponse"
            },
            "example": {
              "code": 401,
              "data": null,
              "message": "未登录或登录已失效"
            }
          }
        }
      },
      "InvalidCredentials": {
        "description": "账号或密码错误",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorApiResponse"
            },
            "example": {
              "code": 401,
              "data": null,
              "message": "账号或密码错误"
            }
          }
        }
      },
      "UserDisabled": {
        "description": "用户已禁用",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorApiResponse"
            },
            "example": {
              "code": 403,
              "data": null,
              "message": "用户已禁用"
            }
          }
        }
      },
      "UsernameTaken": {
        "description": "用户名已存在",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorApiResponse"
            },
            "example": {
              "code": 409,
              "data": null,
              "message": "用户名已存在"
            }
          }
        }
      },
      "AvatarTooLarge": {
        "description": "头像大小超过 10 MiB，或注册请求体过大",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorApiResponse"
            },
            "example": {
              "code": 413,
              "data": null,
              "message": "头像大小不能超过 10 MB"
            }
          }
        }
      },
      "Forbidden": {
        "description": "无权访问目标资源",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorApiResponse"
            },
            "example": {
              "code": 403,
              "data": null,
              "message": "无权访问该会话"
            }
          }
        }
      },
      "NotFound": {
        "description": "目标资源不存在",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorApiResponse"
            },
            "example": {
              "code": 404,
              "data": null,
              "message": "会话不存在"
            }
          }
        }
      },
      "InternalError": {
        "description": "服务器内部错误",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorApiResponse"
            },
            "example": {
              "code": 500,
              "data": null,
              "message": "服务器内部错误"
            }
          }
        }
      }
    },
    "schemas": {
      "RegisterRequest": {
        "type": "object",
        "required": [
          "username",
          "password"
        ],
        "properties": {
          "username": {
            "type": "string",
            "description": "登录账号，必须唯一",
            "example": "alice"
          },
          "password": {
            "type": "string",
            "description": "登录密码",
            "example": "123456"
          },
          "nickname": {
            "type": "string",
            "description": "展示昵称，不传时默认使用 username",
            "example": "Alice"
          },
          "avatar_base64": {
            "type": "string",
            "description": "头像 base64 字符串，可传 data URL 或纯 base64；解码后的图片大小不能超过 10 MiB",
            "example": "data:image/png;base64,iVBORw0KGgo="
          }
        }
      },
      "LoginRequest": {
        "type": "object",
        "required": [
          "username",
          "password"
        ],
        "properties": {
          "username": {
            "type": "string",
            "description": "登录账号",
            "example": "alice"
          },
          "password": {
            "type": "string",
            "description": "登录密码",
            "example": "123456"
          }
        }
      },
      "User": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64",
            "description": "用户内部 ID",
            "example": 1
          },
          "external_id": {
            "type": "string",
            "description": "外部系统用户 ID，本地注册用户通常为空",
            "example": ""
          },
          "username": {
            "type": "string",
            "description": "登录账号",
            "example": "alice"
          },
          "nickname": {
            "type": "string",
            "description": "展示昵称",
            "example": "Alice"
          },
          "avatar_url": {
            "type": "string",
            "description": "头像地址",
            "example": "https://example.com/avatar.png"
          },
          "auth_source": {
            "type": "string",
            "description": "用户来源",
            "example": "local"
          },
          "status": {
            "type": "integer",
            "description": "用户状态，1 正常，0 禁用",
            "example": 1
          }
        }
      },
      "AuthData": {
        "type": "object",
        "properties": {
          "token": {
            "type": "string",
            "description": "JWT token",
            "example": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
          },
          "user": {
            "$ref": "#/components/schemas/User"
          }
        }
      },
      "AuthApiResponse": {
        "type": "object",
        "properties": {
          "code": {
            "type": "integer",
            "example": 200
          },
          "data": {
            "$ref": "#/components/schemas/AuthData"
          },
          "message": {
            "type": "string",
            "example": "登录成功"
          }
        }
      },
      "UserApiResponse": {
        "type": "object",
        "properties": {
          "code": {
            "type": "integer",
            "example": 200
          },
          "data": {
            "$ref": "#/components/schemas/User"
          },
          "message": {
            "type": "string",
            "example": "获取当前用户成功"
          }
        }
      },
      "UserListApiResponse": {
        "type": "object",
        "properties": {
          "code": {
            "type": "integer",
            "example": 200
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/User"
            }
          },
          "message": {
            "type": "string",
            "example": "获取用户列表成功"
          }
        }
      },
      "CreateDirectConversationRequest": {
        "type": "object",
        "required": [
          "target_user_id"
        ],
        "properties": {
          "target_user_id": {
            "type": "integer",
            "format": "int64",
            "description": "目标用户 ID",
            "example": 2
          }
        }
      },
      "CreateGroupConversationRequest": {
        "type": "object",
        "required": [
          "title"
        ],
        "properties": {
          "title": {
            "type": "string",
            "description": "群聊名称",
            "example": "测试群"
          },
          "avatar_url": {
            "type": "string",
            "description": "群聊头像",
            "example": ""
          },
          "member_ids": {
            "type": "array",
            "description": "群成员用户 ID，当前用户会由服务端自动补齐",
            "items": {
              "type": "integer",
              "format": "int64"
            },
            "example": [
              2,
              3
            ]
          }
        }
      },
      "ConversationMember": {
        "type": "object",
        "properties": {
          "user_id": {
            "type": "integer",
            "format": "int64",
            "example": 1
          },
          "role": {
            "type": "string",
            "example": "owner"
          },
          "status": {
            "type": "string",
            "example": "active"
          }
        }
      },
      "Conversation": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64",
            "example": 1
          },
          "type": {
            "type": "string",
            "example": "group"
          },
          "direct_key": {
            "type": "string",
            "example": "1:2"
          },
          "title": {
            "type": "string",
            "example": "测试群"
          },
          "avatar_url": {
            "type": "string",
            "example": ""
          },
          "owner_id": {
            "type": "integer",
            "format": "int64",
            "example": 1
          },
          "member_count": {
            "type": "integer",
            "format": "int64",
            "example": 3
          },
          "last_message_id": {
            "type": "integer",
            "format": "int64",
            "example": 0
          },
          "last_message_at": {
            "type": "string",
            "example": ""
          },
          "members": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ConversationMember"
            }
          }
        }
      },
      "ConversationListItem": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64",
            "example": 1
          },
          "type": {
            "type": "string",
            "example": "direct"
          },
          "direct_key": {
            "type": "string",
            "example": "1:2"
          },
          "title": {
            "type": "string",
            "example": ""
          },
          "avatar_url": {
            "type": "string",
            "example": ""
          },
          "owner_id": {
            "type": "integer",
            "format": "int64",
            "example": 0
          },
          "member_count": {
            "type": "integer",
            "format": "int64",
            "example": 2
          },
          "last_message_id": {
            "type": "integer",
            "format": "int64",
            "example": 0
          },
          "last_message_at": {
            "type": "string",
            "example": ""
          },
          "last_message": {
            "$ref": "#/components/schemas/Message"
          },
          "unread_count": {
            "type": "integer",
            "format": "int64",
            "example": 3
          },
          "last_read_message_id": {
            "type": "integer",
            "format": "int64",
            "example": 1001
          },
          "unread_revision": {
            "type": "integer",
            "format": "int64",
            "example": 2005,
            "description": "会话未读状态的单调版本，增量事件 revision 小于该值时应丢弃"
          }
        }
      },
      "ConversationApiResponse": {
        "type": "object",
        "properties": {
          "code": {
            "type": "integer",
            "example": 200
          },
          "data": {
            "$ref": "#/components/schemas/Conversation"
          },
          "message": {
            "type": "string",
            "example": "创建群聊成功"
          }
        }
      },
      "ConversationDetailApiResponse": {
        "type": "object",
        "properties": {
          "code": {
            "type": "integer",
            "example": 200
          },
          "data": {
            "$ref": "#/components/schemas/Conversation"
          },
          "message": {
            "type": "string",
            "example": "获取会话详情成功"
          }
        }
      },
      "ConversationListApiResponse": {
        "type": "object",
        "properties": {
          "code": {
            "type": "integer",
            "example": 200
          },
          "data": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ConversationListItem"
            }
          },
          "message": {
            "type": "string",
            "example": "获取会话列表成功"
          }
        }
      },
      "SendMessageRequest": {
        "type": "object",
        "required": [
          "conversation_id",
          "client_msg_id",
          "message_type",
          "content"
        ],
        "properties": {
          "conversation_id": {
            "type": "integer",
            "format": "int64",
            "example": 1,
            "description": "会话 ID"
          },
          "client_msg_id": {
            "type": "string",
            "description": "客户端生成的消息 ID，用于幂等去重",
            "example": "client-001"
          },
          "message_type": {
            "type": "string",
            "description": "消息类型",
            "enum": [
              "text",
              "image",
              "file"
            ],
            "example": "text"
          },
          "content": {
            "type": "object",
            "description": "消息内容，不同消息类型使用不同结构",
            "example": {
              "text": "hello"
            }
          }
        }
      },
      "MarkReadRequest": {
        "type": "object",
        "required": [
          "conversation_id",
          "last_read_message_id"
        ],
        "properties": {
          "conversation_id": {
            "type": "integer",
            "format": "int64",
            "example": 1,
            "description": "会话 ID"
          },
          "last_read_message_id": {
            "type": "integer",
            "format": "int64",
            "example": 1001,
            "description": "最后已读消息 ID"
          }
        }
      },
      "Message": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64",
            "example": 1001
          },
          "conversation_id": {
            "type": "integer",
            "format": "int64",
            "example": 1
          },
          "sender_id": {
            "type": "integer",
            "format": "int64",
            "example": 1
          },
          "client_msg_id": {
            "type": "string",
            "example": "client-001"
          },
          "message_type": {
            "type": "string",
            "example": "text"
          },
          "content": {
            "type": "object",
            "example": {
              "text": "hello"
            }
          },
          "status": {
            "type": "string",
            "example": "normal",
            "enum": [
              "normal",
              "recalled"
            ],
            "description": "消息状态；normal 表示正常展示，recalled 表示已软撤回。列表、搜索和未读统计只返回 normal 消息"
          },
          "sent_at": {
            "type": "string",
            "example": "2026-06-29T12:00:00+08:00"
          }
        }
      },
      "MessagePage": {
        "type": "object",
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Message"
            }
          },
          "next_before_id": {
            "type": "integer",
            "format": "int64",
            "example": 1000
          },
          "has_more": {
            "type": "boolean",
            "example": true
          }
        }
      },
      "ReadState": {
        "type": "object",
        "properties": {
          "conversation_id": {
            "type": "integer",
            "format": "int64",
            "example": 1
          },
          "last_read_message_id": {
            "type": "integer",
            "format": "int64",
            "example": 1001
          },
          "last_read_at": {
            "type": "string",
            "example": "2026-06-29T12:05:00+08:00"
          },
          "unread_count": {
            "type": "integer",
            "format": "int64",
            "example": 0
          },
          "last_message_id": {
            "type": "integer",
            "format": "int64",
            "example": 1001
          },
          "revision": {
            "type": "integer",
            "format": "int64",
            "example": 2002,
            "description": "conversation.unread.changed 的单调版本"
          }
        }
      },
      "MessageApiResponse": {
        "type": "object",
        "properties": {
          "code": {
            "type": "integer",
            "example": 200
          },
          "data": {
            "$ref": "#/components/schemas/Message"
          },
          "message": {
            "type": "string",
            "example": "发送消息成功"
          }
        }
      },
      "MessagePageApiResponse": {
        "type": "object",
        "properties": {
          "code": {
            "type": "integer",
            "example": 200
          },
          "data": {
            "$ref": "#/components/schemas/MessagePage"
          },
          "message": {
            "type": "string",
            "example": "获取历史消息成功"
          }
        }
      },
      "NullApiResponse": {
        "type": "object",
        "properties": {
          "code": {
            "type": "integer",
            "example": 200
          },
          "data": {
            "nullable": true,
            "example": null
          },
          "message": {
            "type": "string",
            "example": "操作成功"
          }
        }
      },
      "ReadStateApiResponse": {
        "type": "object",
        "properties": {
          "code": {
            "type": "integer",
            "example": 200
          },
          "data": {
            "$ref": "#/components/schemas/ReadState"
          },
          "message": {
            "type": "string",
            "example": "标记已读成功"
          }
        }
      },
      "ErrorApiResponse": {
        "type": "object",
        "properties": {
          "code": {
            "type": "integer",
            "example": 400
          },
          "data": {
            "nullable": true,
            "example": null
          },
          "message": {
            "type": "string",
            "description": "接口信息或错误信息",
            "example": "参数校验失败"
          }
        }
      },
      "ExternalLoginRequest": {
        "type": "object",
        "required": [
          "provider",
          "access_token"
        ],
        "properties": {
          "provider": {
            "type": "string",
            "example": "demo"
          },
          "access_token": {
            "type": "string",
            "example": "external-user-001"
          }
        }
      },
      "UpsertDeviceRequest": {
        "type": "object",
        "required": [
          "user_id",
          "data"
        ],
        "properties": {
          "user_id": {
            "type": "integer",
            "format": "int64",
            "example": 1
          },
          "data": {
            "type": "object",
            "additionalProperties": true,
            "example": {
              "platform": "web",
              "device_id": "web-001",
              "app_version": "1.0.0"
            }
          }
        }
      },
      "Device": {
        "type": "object",
        "properties": {
          "id": {
            "type": "integer",
            "format": "int64",
            "example": 1
          },
          "user_id": {
            "type": "integer",
            "format": "int64",
            "example": 1
          },
          "data": {
            "type": "object",
            "additionalProperties": true,
            "example": {
              "platform": "web",
              "device_id": "web-001",
              "app_version": "1.0.0"
            }
          },
          "updated_at": {
            "type": "string",
            "example": "2026-06-29T12:00:00+08:00"
          }
        }
      },
      "AddMembersRequest": {
        "type": "object",
        "required": [
          "conversation_id",
          "user_ids"
        ],
        "properties": {
          "conversation_id": {
            "type": "integer",
            "format": "int64",
            "example": 1,
            "description": "会话 ID"
          },
          "user_ids": {
            "type": "array",
            "description": "待加入群聊的用户 ID 列表",
            "items": {
              "type": "integer",
              "format": "int64"
            },
            "example": [
              2,
              3
            ]
          }
        }
      },
      "UpdateMemberRoleRequest": {
        "type": "object",
        "required": [
          "conversation_id",
          "user_id",
          "role"
        ],
        "properties": {
          "conversation_id": {
            "type": "integer",
            "format": "int64",
            "example": 1,
            "description": "会话 ID"
          },
          "user_id": {
            "type": "integer",
            "format": "int64",
            "example": 2,
            "description": "成员用户 ID"
          },
          "role": {
            "type": "string",
            "description": "成员角色",
            "enum": [
              "owner",
              "admin",
              "member"
            ],
            "example": "admin"
          }
        }
      },
      "UpdateGroupProfileRequest": {
        "type": "object",
        "required": [
          "conversation_id",
          "title"
        ],
        "properties": {
          "conversation_id": {
            "type": "integer",
            "format": "int64",
            "example": 1,
            "description": "会话 ID"
          },
          "title": {
            "type": "string",
            "example": "新的群名称"
          },
          "avatar_url": {
            "type": "string",
            "example": "https://example.com/group.png"
          }
        }
      },
      "Presence": {
        "type": "object",
        "properties": {
          "user_id": {
            "type": "integer",
            "format": "int64",
            "example": 1
          },
          "online": {
            "type": "boolean",
            "example": true
          },
          "connection_count": {
            "type": "integer",
            "example": 2
          },
          "last_active_at": {
            "type": "string",
            "example": "2026-06-29T12:00:00+08:00"
          },
          "last_seen_at": {
            "type": "string",
            "example": "2026-06-29T12:00:00+08:00"
          },
          "revision": {
            "type": "integer",
            "format": "int64",
            "example": 15,
            "description": "presence.changed 的单调版本"
          }
        }
      },
      "UserPresence": {
        "type": "object",
        "description": "用户资料和在线状态的合并结构",
        "allOf": [
          {
            "$ref": "#/components/schemas/User"
          },
          {
            "$ref": "#/components/schemas/Presence"
          }
        ]
      },
      "BatchPresenceRequest": {
        "type": "object",
        "required": [
          "type"
        ],
        "properties": {
          "type": {
            "type": "string",
            "description": "人员范围：all 全部、online 在线、offline 离线、specified 指定人员",
            "enum": [
              "all",
              "online",
              "offline",
              "specified"
            ],
            "example": "all"
          },
          "user_ids": {
            "type": "array",
            "description": "type=specified 时必传的用户 ID 列表",
            "items": {
              "type": "integer",
              "format": "int64"
            },
            "example": [
              1,
              2
            ]
          }
        }
      },
      "MessageReceipt": {
        "type": "object",
        "properties": {
          "message_id": {
            "type": "integer",
            "format": "int64",
            "example": 1001
          },
          "user_id": {
            "type": "integer",
            "format": "int64",
            "example": 2
          },
          "status": {
            "type": "string",
            "enum": [
              "unread",
              "read"
            ],
            "example": "read",
            "description": "当前用户对消息的回执状态"
          },
          "updated_at": {
            "type": "string",
            "example": "2026-06-29T12:00:00+08:00"
          }
        }
      },
      "Resource": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "image",
              "video"
            ],
            "example": "image"
          },
          "url": {
            "type": "string",
            "example": "/api/static/images/1/1780000000000.png"
          }
        }
      },
      "ConversationIDRequest": {
        "type": "object",
        "required": [
          "conversation_id"
        ],
        "properties": {
          "conversation_id": {
            "type": "integer",
            "format": "int64",
            "example": 1,
            "description": "会话 ID"
          }
        }
      },
      "RemoveMemberRequest": {
        "type": "object",
        "required": [
          "conversation_id",
          "user_id"
        ],
        "properties": {
          "conversation_id": {
            "type": "integer",
            "format": "int64",
            "example": 1,
            "description": "会话 ID"
          },
          "user_id": {
            "type": "integer",
            "format": "int64",
            "example": 2,
            "description": "待移除用户 ID"
          }
        }
      },
      "ListMessagesRequest": {
        "type": "object",
        "required": [
          "conversation_id"
        ],
        "properties": {
          "conversation_id": {
            "type": "integer",
            "format": "int64",
            "example": 1,
            "description": "会话 ID"
          },
          "before_id": {
            "type": "integer",
            "format": "int64",
            "example": 1001,
            "description": "向前分页游标，不传表示从最新消息开始"
          },
          "limit": {
            "type": "integer",
            "description": "每页数量，最大 100",
            "default": 20,
            "maximum": 100,
            "example": 20
          }
        }
      },
      "SearchConversationMessagesRequest": {
        "type": "object",
        "required": [
          "conversation_id",
          "keyword"
        ],
        "properties": {
          "conversation_id": {
            "type": "integer",
            "format": "int64",
            "example": 1,
            "description": "会话 ID"
          },
          "keyword": {
            "type": "string",
            "description": "搜索关键词",
            "example": "hello"
          },
          "limit": {
            "type": "integer",
            "description": "返回数量，最大 100",
            "default": 20,
            "maximum": 100,
            "example": 20
          }
        }
      },
      "SearchMyMessagesRequest": {
        "type": "object",
        "required": [
          "keyword"
        ],
        "properties": {
          "keyword": {
            "type": "string",
            "description": "搜索关键词",
            "example": "hello"
          },
          "limit": {
            "type": "integer",
            "description": "返回数量，最大 100",
            "default": 20,
            "maximum": 100,
            "example": 20
          }
        }
      },
      "RecallMessageRequest": {
        "type": "object",
        "required": [
          "message_id"
        ],
        "properties": {
          "message_id": {
            "type": "integer",
            "format": "int64",
            "example": 1001,
            "description": "要撤回的消息 ID"
          }
        }
      },
      "MessageReceiptRequest": {
        "type": "object",
        "required": [
          "message_id"
        ],
        "properties": {
          "message_id": {
            "type": "integer",
            "format": "int64",
            "example": 1001,
            "description": "消息 ID"
          }
        }
      },
      "PresenceRequest": {
        "type": "object",
        "required": [
          "user_id"
        ],
        "properties": {
          "user_id": {
            "type": "integer",
            "format": "int64",
            "example": 2,
            "description": "用户 ID"
          }
        }
      }
    }
  }
}
