- 替换所有文件中的品牌名称引用,包括App.vue、pages.json、uni.scss等 - 更新全局样式、导航栏标题和组件中的文案显示 - 修改分享功能中的应用名称显示 - 调整聊天页面的输入栏键盘适配逻辑 - 在多个页面组件中添加事件发射声明以支持交互功能
12 KiB
12 KiB
碰盏日记 - 私信模块接口文档
版本: v1.0 日期: 2026-07-21 模块: 私信(实时通信) 小程序: 碰盏日记(uni-app 微信小程序) 基础URL:
https://dev.wash-painting.cn/api/v1WebSocket: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 统一响应格式
{
"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:
{
"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:
{
"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:
{ "success": true }
业务规则:
- 将该会话中所有对方发来的消息标记为 read
- 会话的 unread 清零
- 幂等处理
3.4 获取未读消息总数
GET /chat/unread-count
响应 data:
{ "total": 3 }
| 字段 | 类型 | 说明 |
|---|---|---|
| total | int | 所有会话未读消息总和 |
4. WebSocket 协议
4.1 连接
wss://dev.wash-painting.cn/ws?token={jwt_token}
- 连接建立后服务端验证 token,无效则返回 close code 4001
- 连接成功后服务端推送:
{ "type": "connected", "data": { "userId": "user_001" } }
4.2 消息帧格式
所有 WebSocket 消息均为 JSON 文本帧:
{
"type": "消息类型",
"data": { ... },
"ts": 1721560000000
}
| 字段 | 类型 | 说明 |
|---|---|---|
| type | string | 消息类型标识 |
| data | object | 业务数据(type=ping/pong 时可省略) |
| ts | long | 客户端发送时间戳(毫秒),服务端原样返回用于延迟计算 |
4.3 客户端 → 服务端
发送聊天消息
{
"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匹配) |
发送输入状态
{
"type": "typing",
"data": { "receiverId": "user_002" },
"ts": 1721560000000
}
发送已读回执
{
"type": "read",
"data": {
"conversationId": "conv_001",
"lastMsgId": "msg_006"
},
"ts": 1721560000000
}
| 字段 | 类型 | 说明 |
|---|---|---|
| conversationId | string | 会话ID |
| lastMsgId | string | 已读到的最后一条消息ID |
心跳
{ "type": "ping" }
4.4 服务端 → 客户端
推送新消息
{
"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)
{
"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。
推送输入状态
{
"type": "typing",
"data": { "senderId": "user_002" }
}
推送已读回执
{
"type": "read",
"data": {
"conversationId": "conv_001",
"readerId": "user_002"
}
}
前端收到后将该会话中自己发送的消息 status 更新为 read。
心跳响应
{ "type": "pong" }
4.5 心跳与重连机制(前端已实现,后端需配合)
| 机制 | 说明 |
|---|---|
| 心跳间隔 | 前端每 30秒 发送 ping,后端收到后立即回复 pong |
| 超时判定 | 前端 60秒 未收到 pong 则主动断开重连 |
| 重连策略 | 指数退避:1s → 2s → 4s → 8s → ... → 最大30s |
| 离线消息 | 用户重新连接后,后端应推送离线期间收到的消息(按时间正序) |
| 连接互踢 | 同一用户多端登录时,旧连接推送 {"type":"kicked"} 后关闭 |
5. 数据库表设计参考
5.1 会话表 (conversations)
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)
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)
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):
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):
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 注意事项
- 字段名严格一致:前端已按本文档字段名渲染,请勿更改命名
- timestamp 为毫秒时间戳:WebSocket 消息中的时间使用 long 型毫秒时间戳
- clientMsgId 必须原样返回:ACK 中需包含客户端发送的 clientMsgId,前端依赖此字段匹配本地消息
- conversationId 生成规则:建议用双方 userId 排序拼接(如
conv_{minId}_{maxId}),确保两人间唯一 - 头像可为空:avatar 字段允许空字符串,前端有默认占位头像
- 内容安全:消息内容需接入微信内容安全审核(msgSecCheck / imgSecCheck)
- 幂等性:标记已读接口需幂等,重复调用不报错
- 离线消息:用户重连后需推送离线消息,或前端通过 REST 接口拉取(当前方案为 REST 拉取)