feat(circle): 集成HaveADrink SDK实现酒友圈功能
- 集成HaveADrink SDK版本从1.0.4升级至1.0.8 - 实现酒友圈API接口,替换原有的mock数据 - 添加地理位置权限配置(requiredPrivateInfos) - 重构API调用方式,统一使用client实例调用真实接口 - 更新WebSocket协议与SDK保持一致,支持聊天、输入状态、已读回执等功能 - 实现游标分页获取动态信息流 - 添加文件上传API支持图片上传 - 优化API错误处理,支持业务级错误提示 - 调整酒局状态显示,新增upcoming待开始状态 - 优化自我感觉标签映射逻辑,支持更多状态类型 - 更新websocket连接认证方式,使用Authorization header传递token - 添加服务端连接确认和被踢下线事件处理
This commit is contained in:
+90
-72
@@ -32,8 +32,17 @@ function httpRequest(url, params) {
|
||||
success: (res) => {
|
||||
const code = res.statusCode
|
||||
if (code === 200 || code === 201 || code === 204) {
|
||||
// 业务级错误:HTTP 200 但 fail:true
|
||||
if (res.data && res.data.fail === true) {
|
||||
const bizMsg = res.data.msg || '操作失败'
|
||||
if (!silent) {
|
||||
uni.showToast({ title: bizMsg, icon: 'none', duration: 2500 })
|
||||
}
|
||||
reject({ msg: bizMsg, code })
|
||||
return
|
||||
}
|
||||
resolve(res.data)
|
||||
} else if (code === 401 || code === 418) {
|
||||
} else if (code === 401 ) {
|
||||
// token/refresh_token 过期或无效,清除登录态
|
||||
uni.removeStorageSync('auth_token')
|
||||
uni.removeStorageSync('refresh_token')
|
||||
@@ -76,8 +85,8 @@ function uploadRequest(url, params) {
|
||||
|
||||
uni.uploadFile({
|
||||
url: url,
|
||||
filePath: params.data.fileName || params.data.filePath,
|
||||
name: params.data.name || 'file',
|
||||
filePath: params.data.image || params.data.fileName || params.data.filePath,
|
||||
name: params.data.name || 'image',
|
||||
header: headers,
|
||||
formData: params.data.formData || {},
|
||||
success: (uploadRes) => {
|
||||
@@ -156,137 +165,146 @@ export async function refreshAuthToken() {
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 酒友圈 API(当前返回 Mock 数据,后端就绪后切换为 SDK 调用)
|
||||
// 酒友圈 API(基于 HaveADrink SDK 真实接口)
|
||||
// ==========================================
|
||||
import { MOCK_FEEDS, MOCK_COMMENTS, MOCK_FRIENDS, MOCK_FRIEND_REQUESTS, MOCK_EVENTS, MOCK_CONVERSATIONS, MOCK_MESSAGES } from './mock-data'
|
||||
|
||||
/** 模拟网络延迟 */
|
||||
function mockDelay(data, ms = 300) {
|
||||
return new Promise(resolve => setTimeout(() => resolve({ data }), ms))
|
||||
}
|
||||
|
||||
/** 获取动态信息流 */
|
||||
export function GetCircleFeeds({ page = 1, pageSize = 10 } = {}) {
|
||||
// TODO: 后端就绪后替换为 client.GetCircleFeeds(...)
|
||||
const start = (page - 1) * pageSize
|
||||
const list = MOCK_FEEDS.slice(start, start + pageSize)
|
||||
return mockDelay({ list, total: MOCK_FEEDS.length, hasMore: start + pageSize < MOCK_FEEDS.length })
|
||||
/** 获取动态信息流(游标分页) */
|
||||
export async function GetCircleFeeds({ lastId = 0, pageSize = 10 } = {}) {
|
||||
const res = await client.GetFeeds({ lastId, pageSize })
|
||||
return { data: res }
|
||||
}
|
||||
|
||||
/** 点赞动态 */
|
||||
export function LikeFeed({ feedId }) {
|
||||
return mockDelay({ success: true })
|
||||
export async function LikeFeed({ feedId }) {
|
||||
const res = await client.LikeFeed({ id: feedId })
|
||||
return { data: res }
|
||||
}
|
||||
|
||||
/** 取消点赞 */
|
||||
export function UnlikeFeed({ feedId }) {
|
||||
return mockDelay({ success: true })
|
||||
export async function UnlikeFeed({ feedId }) {
|
||||
const res = await client.UnlikeFeed({ id: feedId })
|
||||
return { data: res }
|
||||
}
|
||||
|
||||
/** 获取动态评论 */
|
||||
export function GetFeedComments({ feedId }) {
|
||||
const list = MOCK_COMMENTS[feedId] || []
|
||||
return mockDelay({ list })
|
||||
export async function GetFeedComments({ feedId }) {
|
||||
const res = await client.GetFeedComments({ id: feedId })
|
||||
return { data: res }
|
||||
}
|
||||
|
||||
/** 发表评论 */
|
||||
export function AddComment({ feedId, content }) {
|
||||
return mockDelay({ success: true, comment: { id: 'c_' + Date.now(), content, time: '刚刚' } })
|
||||
export async function AddComment({ feedId, content }) {
|
||||
const res = await client.AddComment({ id: feedId, content })
|
||||
return { data: res }
|
||||
}
|
||||
|
||||
/** 发布动态 */
|
||||
export function PublishFeed({ text, images, recordId, visibility }) {
|
||||
return mockDelay({ success: true, feedId: 'feed_' + Date.now() })
|
||||
export async function PublishFeed({ text, images, recordId, visibility }) {
|
||||
const res = await client.PublishFeed({ text, images, recordId, visibility })
|
||||
return { data: res }
|
||||
}
|
||||
|
||||
/** 获取酒友列表 */
|
||||
export function GetFriends({ keyword = '' } = {}) {
|
||||
const list = keyword
|
||||
? MOCK_FRIENDS.filter(f => f.nickname.includes(keyword))
|
||||
: MOCK_FRIENDS
|
||||
return mockDelay({ list })
|
||||
export async function GetFriends({ keyword = '' } = {}) {
|
||||
const res = await client.GetFriends({ keyword })
|
||||
return { data: res }
|
||||
}
|
||||
|
||||
/** 获取好友请求 */
|
||||
export function GetFriendRequests() {
|
||||
return mockDelay({ list: MOCK_FRIEND_REQUESTS })
|
||||
export async function GetFriendRequests() {
|
||||
const res = await client.GetFriendRequests({})
|
||||
return { data: res }
|
||||
}
|
||||
|
||||
/** 发送好友请求 */
|
||||
export function SendFriendRequest({ userId }) {
|
||||
return mockDelay({ success: true })
|
||||
export async function SendFriendRequest({ userId, message = '' }) {
|
||||
const res = await client.SendFriendRequest({ userId, message })
|
||||
return { data: res }
|
||||
}
|
||||
|
||||
/** 接受好友请求 */
|
||||
export function AcceptFriendRequest({ requestId }) {
|
||||
return mockDelay({ success: true })
|
||||
export async function AcceptFriendRequest({ requestId }) {
|
||||
const res = await client.AcceptFriendRequest({ id: requestId })
|
||||
return { data: res }
|
||||
}
|
||||
|
||||
/** 删除酒友 */
|
||||
export function RemoveFriend({ userId }) {
|
||||
return mockDelay({ success: true })
|
||||
export async function RemoveFriend({ userId }) {
|
||||
const res = await client.RemoveFriend({ userId })
|
||||
return { data: res }
|
||||
}
|
||||
|
||||
/** 获取酒局列表 */
|
||||
export function GetEvents({ status = '' } = {}) {
|
||||
const list = status ? MOCK_EVENTS.filter(e => e.status === status) : MOCK_EVENTS
|
||||
return mockDelay({ list })
|
||||
export async function GetEvents({ status = '' } = {}) {
|
||||
const res = await client.GetEvents({ status })
|
||||
return { data: res }
|
||||
}
|
||||
|
||||
/** 获取酒局详情 */
|
||||
export function GetEventDetail({ eventId }) {
|
||||
const evt = MOCK_EVENTS.find(e => e.id === eventId) || MOCK_EVENTS[0]
|
||||
return mockDelay(evt)
|
||||
export async function GetEventDetail({ eventId }) {
|
||||
const res = await client.GetEventDetail({ id: eventId })
|
||||
return { data: res }
|
||||
}
|
||||
|
||||
/** 发起酒局 */
|
||||
export function CreateEvent({ title, time, location, maxPeople, note }) {
|
||||
return mockDelay({ success: true, eventId: 'evt_' + Date.now() })
|
||||
export async function CreateEvent({ title, time, location, maxPeople, note, latitude, longitude }) {
|
||||
const geo = (latitude != null && longitude != null) ? { latitude, longitude } : undefined
|
||||
const res = await client.CreateEvent({ title, time, location, maxPeople, geo, note })
|
||||
return { data: res }
|
||||
}
|
||||
|
||||
/** 报名酒局 */
|
||||
export function JoinEvent({ eventId }) {
|
||||
return mockDelay({ success: true })
|
||||
export async function JoinEvent({ eventId }) {
|
||||
const res = await client.JoinEvent({ id: eventId })
|
||||
return { data: res }
|
||||
}
|
||||
|
||||
/** 取消报名 */
|
||||
export function QuitEvent({ eventId }) {
|
||||
return mockDelay({ success: true })
|
||||
export async function QuitEvent({ eventId }) {
|
||||
const res = await client.QuitEvent({ id: eventId })
|
||||
return { data: res }
|
||||
}
|
||||
|
||||
/** 酒局签到 */
|
||||
export function CheckInEvent({ eventId }) {
|
||||
return mockDelay({ success: true })
|
||||
export async function CheckInEvent({ eventId }) {
|
||||
const res = await client.CheckInEvent({ id: eventId })
|
||||
return { data: res }
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 私信 API(当前返回 Mock 数据,后端就绪后切换为 SDK 调用)
|
||||
// 私信 API(基于 HaveADrink SDK 真实接口)
|
||||
// ==========================================
|
||||
|
||||
/** 获取会话列表 */
|
||||
export function GetConversations() {
|
||||
// TODO: 后端就绪后替换为 client.GetConversations(...)
|
||||
return mockDelay({ list: MOCK_CONVERSATIONS })
|
||||
export async function GetConversations() {
|
||||
const res = await client.GetConversations({})
|
||||
return { data: res }
|
||||
}
|
||||
|
||||
/** 获取历史消息 */
|
||||
export function GetMessages({ conversationId, page = 1, pageSize = 20 } = {}) {
|
||||
// TODO: 后端就绪后替换为 client.GetMessages(...)
|
||||
const all = MOCK_MESSAGES[conversationId] || []
|
||||
const start = (page - 1) * pageSize
|
||||
const list = all.slice(start, start + pageSize)
|
||||
return mockDelay({ list, hasMore: start + pageSize < all.length })
|
||||
/** 获取历史消息(游标分页) */
|
||||
export async function GetMessages({ conversationId, lastID = 0, pageSize = 20 } = {}) {
|
||||
const res = await client.GetMessages({ conversationId, lastID, pageSize })
|
||||
return { data: res }
|
||||
}
|
||||
|
||||
/** 标记会话已读 */
|
||||
export function MarkRead({ conversationId }) {
|
||||
// TODO: 后端就绪后替换为 client.MarkRead(...)
|
||||
return mockDelay({ success: true })
|
||||
export async function MarkRead({ conversationId }) {
|
||||
const res = await client.MarkRead({ id: conversationId })
|
||||
return { data: res }
|
||||
}
|
||||
|
||||
/** 获取未读消息总数 */
|
||||
export function GetUnreadCount() {
|
||||
// TODO: 后端就绪后替换为 client.GetUnreadCount(...)
|
||||
const total = MOCK_CONVERSATIONS.reduce((sum, c) => sum + c.unread, 0)
|
||||
return mockDelay({ total })
|
||||
export async function GetUnreadCount() {
|
||||
const res = await client.GetUnreadCount({})
|
||||
return { data: res }
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 文件上传 API
|
||||
// ==========================================
|
||||
|
||||
/** 上传图片(返回 url) */
|
||||
export async function UploadImage({ filePath }) {
|
||||
const res = await client.UploadImage({ image: filePath })
|
||||
return { data: res }
|
||||
}
|
||||
|
||||
+57
-22
@@ -1,9 +1,29 @@
|
||||
/* 碰盏日记 - WebSocket 管理器
|
||||
* 单例模式,负责私信实时通信
|
||||
* 协议格式与 HaveADrink SDK ChatWebSocket 一致:{ mesgType, data }
|
||||
* 支持心跳、自动重连、消息队列
|
||||
*/
|
||||
|
||||
const WS_HOST = 'wss://dev.wash-painting.cn/ws'
|
||||
const WS_HOST = 'wss://dev.wash-painting.cn/api/have_a_drink/v1/chat/chat'
|
||||
|
||||
// 客户端消息类型(与 SDK ClientMesgType 枚举值一致)
|
||||
const ClientMesgType = {
|
||||
Chat: 'chat',
|
||||
Typing: 'typing',
|
||||
Read: 'read',
|
||||
Ping: 'ping'
|
||||
}
|
||||
|
||||
// 服务端消息类型(与 SDK ServerMesgType 枚举值一致)
|
||||
const ServerMesgType = {
|
||||
Chat: 'chat',
|
||||
Typing: 'typing',
|
||||
Read: 'read',
|
||||
Ack: 'ack',
|
||||
Pong: 'pong',
|
||||
Connected: 'connected',
|
||||
Kicked: 'kicked'
|
||||
}
|
||||
|
||||
class WebSocketManager {
|
||||
constructor() {
|
||||
@@ -21,6 +41,7 @@ class WebSocketManager {
|
||||
this.heartbeatWait = 60000 // 60s 无 pong 则重连
|
||||
this.manualClose = false
|
||||
this.token = ''
|
||||
this.userId = '' // 连接成功后服务端返回的用户ID
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -37,11 +58,14 @@ class WebSocketManager {
|
||||
this.manualClose = false
|
||||
this.isConnecting = true
|
||||
|
||||
const url = `${WS_HOST}?token=${this.token}`
|
||||
console.log('[WS] 正在连接...', url)
|
||||
const url = WS_HOST
|
||||
console.log('[WS] 正在连接...')
|
||||
|
||||
this.socketTask = uni.connectSocket({
|
||||
url,
|
||||
header: {
|
||||
'Authorization': `Bearer ${this.token}`
|
||||
},
|
||||
complete: () => {}
|
||||
})
|
||||
|
||||
@@ -95,17 +119,17 @@ class WebSocketManager {
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送消息
|
||||
* @param {string} type - 消息类型: chat | typing | read | ping
|
||||
* 发送消息(与 SDK writeClient* 方法格式一致)
|
||||
* @param {string} mesgType - 消息类型: chat | typing | read | ping
|
||||
* @param {object} data - 消息数据
|
||||
*/
|
||||
send(type, data = {}) {
|
||||
const payload = JSON.stringify({ type, data, ts: Date.now() })
|
||||
send(mesgType, data = {}) {
|
||||
const payload = JSON.stringify({ mesgType, data })
|
||||
if (this.isConnected && this.socketTask) {
|
||||
this.socketTask.send({ data: payload })
|
||||
} else {
|
||||
// 断线暂存队列
|
||||
if (type !== 'ping') {
|
||||
// 断线暂存队列(ping 不暂存)
|
||||
if (mesgType !== ClientMesgType.Ping) {
|
||||
this.messageQueue.push(payload)
|
||||
}
|
||||
// 尝试重连
|
||||
@@ -117,7 +141,7 @@ class WebSocketManager {
|
||||
|
||||
/**
|
||||
* 订阅事件
|
||||
* @param {string} event - message | typing | read | ack | connect | disconnect
|
||||
* @param {string} event - message | typing | read | ack | connected | kicked | connect | disconnect
|
||||
* @param {function} callback
|
||||
*/
|
||||
on(event, callback) {
|
||||
@@ -143,27 +167,38 @@ class WebSocketManager {
|
||||
|
||||
// ========== 内部方法 ==========
|
||||
|
||||
/** 处理收到的消息 */
|
||||
/** 处理收到的消息(SDK ServerMesgType 协议) */
|
||||
_handleMessage(msg) {
|
||||
const { type, data } = msg
|
||||
switch (type) {
|
||||
case 'pong':
|
||||
this._onPong()
|
||||
break
|
||||
case 'chat':
|
||||
const { mesgType, data } = msg
|
||||
switch (mesgType) {
|
||||
case ServerMesgType.Chat:
|
||||
this._emit('message', data)
|
||||
break
|
||||
case 'typing':
|
||||
case ServerMesgType.Typing:
|
||||
this._emit('typing', data)
|
||||
break
|
||||
case 'read':
|
||||
case ServerMesgType.Read:
|
||||
this._emit('read', data)
|
||||
break
|
||||
case 'ack':
|
||||
case ServerMesgType.Ack:
|
||||
this._emit('ack', data)
|
||||
break
|
||||
case ServerMesgType.Pong:
|
||||
this._onPong()
|
||||
break
|
||||
case ServerMesgType.Connected:
|
||||
// 服务端验证 token 成功后推送,携带 userId
|
||||
this.userId = data && data.userId ? data.userId : ''
|
||||
console.log('[WS] 服务端确认连接, userId:', this.userId)
|
||||
this._emit('connected', data)
|
||||
break
|
||||
case ServerMesgType.Kicked:
|
||||
console.warn('[WS] 被踢下线')
|
||||
this._emit('kicked', data)
|
||||
this.disconnect()
|
||||
break
|
||||
default:
|
||||
console.log('[WS] 未知消息类型', type)
|
||||
console.log('[WS] 未知消息类型', mesgType)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -181,7 +216,7 @@ class WebSocketManager {
|
||||
_startHeartbeat() {
|
||||
this._stopHeartbeat()
|
||||
this.heartbeatTimer = setInterval(() => {
|
||||
this.send('ping')
|
||||
this.send(ClientMesgType.Ping, {})
|
||||
// 设置超时检测
|
||||
this.heartbeatTimeout = setTimeout(() => {
|
||||
console.warn('[WS] 心跳超时,触发重连')
|
||||
|
||||
Reference in New Issue
Block a user