# 碰盏日记 - 私信模块接口文档 > 版本: v1.0 > 日期: 2026-07-21 > 模块: 私信(实时通信) > 小程序: 碰盏日记(uni-app 微信小程序) > 基础URL: `https://dev.wash-painting.cn/api/v1` > WebSocket: `wss://dev.wash-painting.cn/ws` --- ## 1. 模块概述 私信是酒友圈的实时通信子模块,允许酒友之间一对一发送消息。 | 子功能 | 说明 | |--------|------| | 会话列表 | 展示所有聊天会话,含最后消息、未读数 | | 聊天记录 | 分页加载历史消息 | | 实时收发 | 通过 WebSocket 实时推送/接收消息 | | 已读回执 | 标记消息已读,通知对方 | | 输入状态 | 实时显示"对方正在输入..." | **前端当前状态:** 所有 REST 接口已在 `common/api.js` 中预留方法(暂用 Mock);WebSocket 管理器已在 `common/websocket.js` 中实现(含心跳、重连、消息队列)。后端实现后前端仅需切换调用方式。**请严格按照本文档的字段名和协议格式实现**。 --- ## 2. 通用约定 ### 2.1 鉴权 - REST 请求头:`Authorization: Bearer {token}` - WebSocket 连接:`wss://dev.wash-painting.cn/ws?token={token}` - 所有接口均需登录态 ### 2.2 REST 统一响应格式 ```json { "code": 0, "message": "success", "data": {} } ``` ### 2.3 错误码 | code | 含义 | |------|------| | 0 | 成功 | | 400 | 参数错误 | | 401 | 未登录/token过期 | | 403 | 无权限(如:非酒友关系不可发私信) | | 404 | 资源不存在 | | 500 | 服务器错误 | ### 2.4 时间格式 - REST 接口时间字段使用 ISO 8601:`2026-07-21T22:30:00.000Z` - WebSocket 消息中 timestamp 使用**毫秒时间戳**(如 `1721560000000`),便于前端排序 --- ## 3. REST 接口 ### 3.1 获取会话列表 ``` GET /chat/conversations ``` **查询参数:** 无 **响应 data:** ```json { "list": [ { "id": "conv_001", "friendId": "user_002", "nickname": "老张", "avatar": "https://xxx/avatar.jpg", "lastMessage": "今晚一起喝点?", "lastTime": "2026-07-21T10:32:00.000Z", "unread": 2, "online": true } ] } ``` **Conversation 字段说明:** | 字段 | 类型 | 说明 | |------|------|------| | id | string | 会话ID | | friendId | string | 对方用户ID | | nickname | string | 对方昵称 | | avatar | string | 对方头像URL(可为空字符串) | | lastMessage | string | 最后一条消息摘要(截取前30字) | | lastTime | string | 最后消息时间 ISO 8601 | | unread | int | 未读消息数 | | online | boolean | 对方是否在线 | **业务规则:** - 按 lastTime 倒序排列 - 仅返回有消息记录的会话 - 仅酒友之间可存在会话 --- ### 3.2 获取历史消息 ``` GET /chat/messages ``` **查询参数:** | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | conversationId | string | 是 | 会话ID | | page | int | 否 | 页码,默认1 | | pageSize | int | 否 | 每页数量,默认20 | **响应 data:** ```json { "list": [ { "id": "msg_001", "conversationId": "conv_001", "senderId": "user_002", "receiverId": "user_001", "type": "text", "content": "今晚一起喝点?", "timestamp": 1721556300000, "status": "read" } ], "hasMore": true } ``` **Message 字段说明:** | 字段 | 类型 | 说明 | |------|------|------| | id | string | 消息ID(服务端生成) | | conversationId | string | 所属会话ID | | senderId | string | 发送者用户ID | | receiverId | string | 接收者用户ID | | type | string | 消息类型:`text` / `image` | | content | string | 消息内容(文本内容 或 图片URL) | | timestamp | long | 发送时间(毫秒时间戳) | | status | string | 消息状态:`sent` / `delivered` / `read` | **业务规则:** - 按 timestamp 正序返回(最早在前) - status 表示该消息对接收方的状态(发送方自己的消息显示对方是否已读) --- ### 3.3 标记会话已读 ``` POST /chat/conversations/:id/read ``` **请求参数:** 无(路径参数 conversationId) **响应 data:** ```json { "success": true } ``` **业务规则:** - 将该会话中所有对方发来的消息标记为 read - 会话的 unread 清零 - 幂等处理 --- ### 3.4 获取未读消息总数 ``` GET /chat/unread-count ``` **响应 data:** ```json { "total": 3 } ``` | 字段 | 类型 | 说明 | |------|------|------| | total | int | 所有会话未读消息总和 | --- ## 4. WebSocket 协议 ### 4.1 连接 ``` wss://dev.wash-painting.cn/ws?token={jwt_token} ``` - 连接建立后服务端验证 token,无效则返回 close code 4001 - 连接成功后服务端推送: ```json { "type": "connected", "data": { "userId": "user_001" } } ``` ### 4.2 消息帧格式 所有 WebSocket 消息均为 JSON 文本帧: ```json { "type": "消息类型", "data": { ... }, "ts": 1721560000000 } ``` | 字段 | 类型 | 说明 | |------|------|------| | type | string | 消息类型标识 | | data | object | 业务数据(type=ping/pong 时可省略) | | ts | long | 客户端发送时间戳(毫秒),服务端原样返回用于延迟计算 | --- ### 4.3 客户端 → 服务端 #### 发送聊天消息 ```json { "type": "chat", "data": { "receiverId": "user_002", "content": "今晚一起喝点?", "msgType": "text", "clientMsgId": "msg_1721560000000_a3f2" }, "ts": 1721560000000 } ``` | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | receiverId | string | 是 | 接收者用户ID | | content | string | 是 | 消息内容(文本 或 图片URL) | | msgType | string | 是 | `text` / `image` | | clientMsgId | string | 是 | 客户端生成的临时消息ID(用于ACK匹配) | #### 发送输入状态 ```json { "type": "typing", "data": { "receiverId": "user_002" }, "ts": 1721560000000 } ``` #### 发送已读回执 ```json { "type": "read", "data": { "conversationId": "conv_001", "lastMsgId": "msg_006" }, "ts": 1721560000000 } ``` | 字段 | 类型 | 说明 | |------|------|------| | conversationId | string | 会话ID | | lastMsgId | string | 已读到的最后一条消息ID | #### 心跳 ```json { "type": "ping" } ``` --- ### 4.4 服务端 → 客户端 #### 推送新消息 ```json { "type": "chat", "data": { "msgId": "msg_srv_001", "senderId": "user_002", "receiverId": "user_001", "conversationId": "conv_001", "content": "好啊,几点?", "msgType": "text", "timestamp": 1721560060000 } } ``` | 字段 | 类型 | 说明 | |------|------|------| | msgId | string | 服务端生成的消息ID | | senderId | string | 发送者ID | | receiverId | string | 接收者ID | | conversationId | string | 会话ID | | content | string | 消息内容 | | msgType | string | `text` / `image` | | timestamp | long | 服务端接收时间(毫秒时间戳) | #### 消息送达确认(ACK) ```json { "type": "ack", "data": { "clientMsgId": "msg_1721560000000_a3f2", "msgId": "msg_srv_001", "status": "sent" } } ``` | 字段 | 类型 | 说明 | |------|------|------| | clientMsgId | string | 客户端原始消息ID(用于匹配) | | msgId | string | 服务端分配的正式消息ID | | status | string | `sent`(已入库) | > 前端收到 ACK 后将本地消息的 id 替换为 msgId,status 更新为 sent。 #### 推送输入状态 ```json { "type": "typing", "data": { "senderId": "user_002" } } ``` #### 推送已读回执 ```json { "type": "read", "data": { "conversationId": "conv_001", "readerId": "user_002" } } ``` > 前端收到后将该会话中自己发送的消息 status 更新为 read。 #### 心跳响应 ```json { "type": "pong" } ``` --- ### 4.5 心跳与重连机制(前端已实现,后端需配合) | 机制 | 说明 | |------|------| | 心跳间隔 | 前端每 **30秒** 发送 `ping`,后端收到后立即回复 `pong` | | 超时判定 | 前端 60秒 未收到 `pong` 则主动断开重连 | | 重连策略 | 指数退避:1s → 2s → 4s → 8s → ... → 最大30s | | 离线消息 | 用户重新连接后,后端应推送离线期间收到的消息(按时间正序) | | 连接互踢 | 同一用户多端登录时,旧连接推送 `{"type":"kicked"}` 后关闭 | --- ## 5. 数据库表设计参考 ### 5.1 会话表 (conversations) ```sql CREATE TABLE conversations ( id VARCHAR(32) PRIMARY KEY, user_a_id VARCHAR(32) NOT NULL COMMENT '用户A(ID较小者)', user_b_id VARCHAR(32) NOT NULL COMMENT '用户B(ID较大者)', last_message VARCHAR(128) COMMENT '最后消息摘要', last_message_time DATETIME COMMENT '最后消息时间', created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, UNIQUE INDEX idx_users (user_a_id, user_b_id), INDEX idx_user_a (user_a_id, updated_at), INDEX idx_user_b (user_b_id, updated_at) ); ``` > 两人之间只有一个会话,user_a_id < user_b_id 保证唯一。 ### 5.2 消息表 (messages) ```sql CREATE TABLE messages ( id VARCHAR(32) PRIMARY KEY, conversation_id VARCHAR(32) NOT NULL, sender_id VARCHAR(32) NOT NULL, receiver_id VARCHAR(32) NOT NULL, type ENUM('text', 'image') DEFAULT 'text', content TEXT NOT NULL, status ENUM('sent', 'delivered', 'read') DEFAULT 'sent', created_at DATETIME DEFAULT CURRENT_TIMESTAMP, INDEX idx_conv_time (conversation_id, created_at), INDEX idx_receiver_status (receiver_id, status) ); ``` ### 5.3 未读计数表 (unread_counts) ```sql CREATE TABLE unread_counts ( id BIGINT AUTO_INCREMENT PRIMARY KEY, user_id VARCHAR(32) NOT NULL, conversation_id VARCHAR(32) NOT NULL, count INT DEFAULT 0, UNIQUE INDEX idx_user_conv (user_id, conversation_id), INDEX idx_user (user_id) ); ``` > 收到新消息时 count+1,标记已读时归零。 --- ## 6. 业务规则汇总 | 规则 | 说明 | |------|------| | 酒友限制 | 仅酒友关系可发私信,非酒友返回 403 | | 消息长度 | 文本消息最长 500 字 | | 图片消息 | content 为图片URL(先调上传接口),前端展示为图片气泡 | | 内容安全 | 文本消息需接入微信 msgSecCheck 审核,图片需接入 imgSecCheck | | 会话自动创建 | 首次发消息时自动创建会话记录 | | 未读计数 | 新消息到达时接收方 unread+1;调用标记已读或 WebSocket read 事件时归零 | | 消息撤回 | v1.0 暂不支持,后续扩展 | | 离线推送 | v1.0 暂不接入微信订阅消息推送,用户上线后拉取未读即可 | --- ## 7. 前端对接说明 ### 7.1 前端调用位置 **REST 接口**(`common/api.js`): ```js GetConversations() // GET /chat/conversations GetMessages({ conversationId, page }) // GET /chat/messages MarkRead({ conversationId }) // POST /chat/conversations/:id/read GetUnreadCount() // GET /chat/unread-count ``` **WebSocket**(`common/websocket.js`): ```js wsManager.connect(token) // 建立连接 wsManager.send('chat', { receiverId, content, msgType, clientMsgId }) wsManager.send('typing', { receiverId }) wsManager.send('read', { conversationId, lastMsgId }) wsManager.on('message', handler) // 收到新消息 wsManager.on('typing', handler) // 对方正在输入 wsManager.on('read', handler) // 对方已读 wsManager.on('ack', handler) // 消息送达确认 ``` ### 7.2 注意事项 1. **字段名严格一致**:前端已按本文档字段名渲染,请勿更改命名 2. **timestamp 为毫秒时间戳**:WebSocket 消息中的时间使用 long 型毫秒时间戳 3. **clientMsgId 必须原样返回**:ACK 中需包含客户端发送的 clientMsgId,前端依赖此字段匹配本地消息 4. **conversationId 生成规则**:建议用双方 userId 排序拼接(如 `conv_{minId}_{maxId}`),确保两人间唯一 5. **头像可为空**:avatar 字段允许空字符串,前端有默认占位头像 6. **内容安全**:消息内容需接入微信内容安全审核(msgSecCheck / imgSecCheck) 7. **幂等性**:标记已读接口需幂等,重复调用不报错 8. **离线消息**:用户重连后需推送离线消息,或前端通过 REST 接口拉取(当前方案为 REST 拉取)