初始化
This commit is contained in:
17
src/api/index.ts
Normal file
17
src/api/index.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* API 统一导出
|
||||
*/
|
||||
export * as authApi from './modules/auth'
|
||||
export * as userApi from './modules/user'
|
||||
export * as contactApi from './modules/contact'
|
||||
export * as conversationApi from './modules/conversation'
|
||||
export * as messageApi from './modules/message'
|
||||
export * as roomApi from './modules/room'
|
||||
export * as momentApi from './modules/moment'
|
||||
export * as attachmentApi from './modules/attachment'
|
||||
export * as systemApi from './modules/system'
|
||||
|
||||
export { wsManager } from './websocket'
|
||||
export type { WebSocketMessage, MessageHandler, SignalHandler, MomentNotifHandler } from './websocket'
|
||||
|
||||
export { default as request } from './request'
|
||||
25
src/api/modules/attachment.ts
Normal file
25
src/api/modules/attachment.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* 附件管理相关 API
|
||||
*/
|
||||
import request from '../request'
|
||||
import type { Attachment, PaginatedResponse } from '@/types/api'
|
||||
|
||||
// 上传附件
|
||||
export function uploadAttachment(filePath: string, type?: 'image' | 'video' | 'file') {
|
||||
return request.upload<Attachment>('/attachments/upload', filePath, 'file', type ? { type } : undefined)
|
||||
}
|
||||
|
||||
// 获取附件信息
|
||||
export function getAttachment(id: number) {
|
||||
return request.get<Attachment>(`/attachments/${id}`)
|
||||
}
|
||||
|
||||
// 获取附件列表
|
||||
export function getAttachments(type?: string, page = 1, pageSize = 20) {
|
||||
return request.get<PaginatedResponse<Attachment>>('/attachments', { type, page, page_size: pageSize })
|
||||
}
|
||||
|
||||
// 删除附件
|
||||
export function deleteAttachment(id: number) {
|
||||
return request.post<void>(`/attachments/delete/${id}`)
|
||||
}
|
||||
30
src/api/modules/auth.ts
Normal file
30
src/api/modules/auth.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* 认证相关 API
|
||||
*/
|
||||
import request from '../request'
|
||||
import type { LoginRequest, LoginResponse, RegisterRequest, SendCodeRequest } from '@/types/api'
|
||||
|
||||
// 用户登录
|
||||
export function login(data: LoginRequest) {
|
||||
return request.post<LoginResponse>('/login', data)
|
||||
}
|
||||
|
||||
// 用户注册
|
||||
export function register(data: RegisterRequest) {
|
||||
return request.post<void>('/register', data)
|
||||
}
|
||||
|
||||
// 发送邮箱验证码
|
||||
export function sendEmailCode(data: SendCodeRequest) {
|
||||
return request.post<void>('/send-email-code', data)
|
||||
}
|
||||
|
||||
// 发送短信验证码
|
||||
export function sendSmsCode(data: SendCodeRequest) {
|
||||
return request.post<void>('/send-sms-code', data)
|
||||
}
|
||||
|
||||
// 检查Token有效性
|
||||
export function checkToken() {
|
||||
return request.get<void>('/check-token')
|
||||
}
|
||||
85
src/api/modules/contact.ts
Normal file
85
src/api/modules/contact.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* 联系人管理相关 API
|
||||
*/
|
||||
import request from '../request'
|
||||
import type { Contact, User, FriendRequest, ContactGroup } from '@/types/api'
|
||||
|
||||
// 获取联系人列表
|
||||
export function getContacts() {
|
||||
return request.get<Contact[]>('/contacts')
|
||||
}
|
||||
|
||||
// 搜索用户
|
||||
export function searchUsers(keyword: string, limit = 20) {
|
||||
return request.get<User[]>('/contacts/search', { keyword, limit })
|
||||
}
|
||||
|
||||
// 添加好友(发送申请)
|
||||
export function addFriend(data: { to_user_id: string; message?: string }) {
|
||||
return request.post<void>('/contacts/add-friend', data)
|
||||
}
|
||||
|
||||
// 获取好友申请列表
|
||||
export function getFriendRequests() {
|
||||
return request.get<FriendRequest[]>('/contacts/friend-requests')
|
||||
}
|
||||
|
||||
// 接受好友申请
|
||||
export function acceptFriendRequest(requestId: number) {
|
||||
return request.post<void>('/contacts/accept-request', { request_id: requestId })
|
||||
}
|
||||
|
||||
// 拒绝好友申请
|
||||
export function rejectFriendRequest(requestId: number) {
|
||||
return request.post<void>('/contacts/reject-request', { request_id: requestId })
|
||||
}
|
||||
|
||||
// 获取分组列表
|
||||
export function getGroups() {
|
||||
return request.get<ContactGroup[]>('/contacts/groups')
|
||||
}
|
||||
|
||||
// 创建分组
|
||||
export function createGroup(data: { group_name: string }) {
|
||||
return request.post<ContactGroup>('/contacts/groups', data)
|
||||
}
|
||||
|
||||
// 更新分组
|
||||
export function updateGroup(id: number, data: { group_name?: string; sort_order?: number }) {
|
||||
return request.post<ContactGroup>(`/contacts/groups/update/${id}`, data)
|
||||
}
|
||||
|
||||
// 删除分组
|
||||
export function deleteGroup(id: number) {
|
||||
return request.post<void>(`/contacts/groups/delete/${id}`)
|
||||
}
|
||||
|
||||
// 获取好友详情
|
||||
export async function getContactDetail(id: string): Promise<Contact> {
|
||||
const response = await request.get<{ contact: any; user: User }>(`/contacts/${id}`)
|
||||
return {
|
||||
...response.contact,
|
||||
id: response.contact.id?.toString() || response.contact.contact_id,
|
||||
user_id: response.contact.contact_id || response.contact.user_id,
|
||||
contact_user_id: response.contact.contact_id,
|
||||
room_id: response.contact.room_id,
|
||||
user: response.user
|
||||
} as Contact
|
||||
}
|
||||
|
||||
// 更新好友信息
|
||||
export function updateContact(id: string, data: {
|
||||
remark_name?: string
|
||||
group_id?: number
|
||||
is_top?: boolean
|
||||
is_muted?: boolean
|
||||
is_special_care?: boolean
|
||||
is_blocked?: boolean
|
||||
}) {
|
||||
return request.post<Contact>(`/contacts/update/${id}`, data)
|
||||
}
|
||||
|
||||
// 删除好友
|
||||
export function deleteContact(id: string) {
|
||||
return request.post<void>(`/contacts/delete/${id}`)
|
||||
}
|
||||
39
src/api/modules/conversation.ts
Normal file
39
src/api/modules/conversation.ts
Normal file
@@ -0,0 +1,39 @@
|
||||
/**
|
||||
* 会话列表相关 API
|
||||
*/
|
||||
import request from '../request'
|
||||
import type { Conversation } from '@/types/conversation'
|
||||
|
||||
// 获取会话列表
|
||||
export function getConversationList() {
|
||||
return request.get<Conversation[]>('/conversations')
|
||||
}
|
||||
|
||||
// 重置未读
|
||||
export function resetUnread(targetId: string) {
|
||||
return request.post<void>('/conversations/reset-unread', {
|
||||
target_id: targetId
|
||||
})
|
||||
}
|
||||
|
||||
// 更新会话标记
|
||||
export function updateConversation(data: {
|
||||
target_id: string
|
||||
is_top?: boolean
|
||||
is_muted?: boolean
|
||||
is_special_care?: boolean
|
||||
}) {
|
||||
return request.post<void>('/conversations/update', data)
|
||||
}
|
||||
|
||||
// 删除会话
|
||||
export function deleteConversation(targetId: string) {
|
||||
return request.post<void>('/conversations/delete', {
|
||||
target_id: targetId
|
||||
})
|
||||
}
|
||||
|
||||
// 根据 room_id 获取或创建会话
|
||||
export function getConversationByRoom(roomId: string) {
|
||||
return request.get<Conversation>(`/conversations/by-room/${roomId}`)
|
||||
}
|
||||
28
src/api/modules/message.ts
Normal file
28
src/api/modules/message.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
/**
|
||||
* 消息管理相关 API
|
||||
*/
|
||||
import request from '../request'
|
||||
import type { ChatMessage, SendMessageRequest, PaginatedResponse } from '@/types/api'
|
||||
|
||||
// 发送消息
|
||||
export function sendMessage(data: SendMessageRequest) {
|
||||
return request.post<void>('/send', data)
|
||||
}
|
||||
|
||||
// 获取历史消息
|
||||
export function getMessages(roomId: string, page = 1, pageSize = 50) {
|
||||
return request.get<PaginatedResponse<ChatMessage>>('/messages', {
|
||||
room_id: roomId,
|
||||
page,
|
||||
page_size: pageSize
|
||||
})
|
||||
}
|
||||
|
||||
// 同步消息(兼容接口)
|
||||
export function syncMessages(roomId: string, page = 1, pageSize = 50) {
|
||||
return request.get<PaginatedResponse<ChatMessage>>('/messages/sync', {
|
||||
room_id: roomId,
|
||||
page,
|
||||
page_size: pageSize
|
||||
})
|
||||
}
|
||||
99
src/api/modules/moment.ts
Normal file
99
src/api/modules/moment.ts
Normal file
@@ -0,0 +1,99 @@
|
||||
/**
|
||||
* 朋友圈相关 API
|
||||
*/
|
||||
import request from '../request'
|
||||
import type {
|
||||
Moment,
|
||||
MomentComment,
|
||||
MomentNotification,
|
||||
CreateMomentRequest,
|
||||
CreateCommentRequest,
|
||||
MarkReadRequest,
|
||||
PaginatedResponse
|
||||
} from '@/types/moment'
|
||||
|
||||
// ==========================================
|
||||
// 动态相关
|
||||
// ==========================================
|
||||
|
||||
// 发布动态
|
||||
export function createMoment(data: CreateMomentRequest) {
|
||||
return request.post<Moment>('/moments', data)
|
||||
}
|
||||
|
||||
// 获取好友动态列表
|
||||
export function getMoments(page = 1, pageSize = 20) {
|
||||
return request.get<PaginatedResponse<Moment>>('/moments', { page, page_size: pageSize })
|
||||
}
|
||||
|
||||
// 获取动态详情
|
||||
export function getMomentDetail(id: number) {
|
||||
return request.get<Moment>(`/moments/${id}`)
|
||||
}
|
||||
|
||||
// 删除动态
|
||||
export function deleteMoment(id: number) {
|
||||
return request.delete(`/moments/${id}`)
|
||||
}
|
||||
|
||||
// 获取指定用户的动态列表
|
||||
export function getUserMoments(userId: string, page = 1, pageSize = 20) {
|
||||
return request.get<PaginatedResponse<Moment>>(`/moments/user/${userId}`, { page, page_size: pageSize })
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 点赞相关
|
||||
// ==========================================
|
||||
|
||||
// 点赞动态
|
||||
export function likeMoment(id: number) {
|
||||
return request.post(`/moments/${id}/like`)
|
||||
}
|
||||
|
||||
// 取消点赞
|
||||
export function unlikeMoment(id: number) {
|
||||
return request.delete(`/moments/${id}/like`)
|
||||
}
|
||||
|
||||
// 获取动态的点赞列表
|
||||
export function getMomentLikes(momentId: number) {
|
||||
return request.get<Array<{ id: number; user_id: string; created_at: string }>>(`/moments/${momentId}/likes`)
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 评论相关
|
||||
// ==========================================
|
||||
|
||||
// 获取动态的评论列表
|
||||
export function getMomentComments(momentId: number) {
|
||||
return request.get<MomentComment[]>(`/moments/${momentId}/comments`)
|
||||
}
|
||||
|
||||
// 发表评论
|
||||
export function createComment(momentId: number, data: CreateCommentRequest) {
|
||||
return request.post<MomentComment>(`/moments/${momentId}/comments`, data)
|
||||
}
|
||||
|
||||
// 删除评论
|
||||
export function deleteComment(commentId: number) {
|
||||
return request.delete(`/moments/comments/${commentId}`)
|
||||
}
|
||||
|
||||
// ==========================================
|
||||
// 通知相关
|
||||
// ==========================================
|
||||
|
||||
// 获取朋友圈通知列表
|
||||
export function getNotifications(page = 1, pageSize = 20) {
|
||||
return request.get<PaginatedResponse<MomentNotification>>('/moments/notifications', { page, page_size: pageSize })
|
||||
}
|
||||
|
||||
// 标记通知为已读
|
||||
export function markNotificationsRead(data: MarkReadRequest) {
|
||||
return request.post('/moments/notifications/read', data)
|
||||
}
|
||||
|
||||
// 获取未读通知数量
|
||||
export function getUnreadCount() {
|
||||
return request.get<{ count: number }>('/moments/notifications/unread-count')
|
||||
}
|
||||
151
src/api/modules/room.ts
Normal file
151
src/api/modules/room.ts
Normal file
@@ -0,0 +1,151 @@
|
||||
/**
|
||||
* 房间/群聊管理相关 API
|
||||
*/
|
||||
import request from '../request'
|
||||
import type { Room, GroupInfo, GroupMember } from '@/types/api'
|
||||
|
||||
// 创建房间(单聊)
|
||||
export function createRoom(data: {
|
||||
room_type: 'p2p' | 'group'
|
||||
members: string[]
|
||||
name?: string
|
||||
avatar?: string
|
||||
}) {
|
||||
return request.post<Room>('/rooms', data)
|
||||
}
|
||||
|
||||
// 获取房间信息
|
||||
export function getRoom(id: string) {
|
||||
return request.get<Room>(`/rooms/${id}`)
|
||||
}
|
||||
|
||||
/**
|
||||
* 群聊相关 API
|
||||
*/
|
||||
|
||||
// 创建群聊
|
||||
export function createGroup(data: {
|
||||
name: string
|
||||
avatar?: string
|
||||
member_ids: string[]
|
||||
admin_ids?: string[]
|
||||
}) {
|
||||
return request.post<GroupInfo>('/groups', data)
|
||||
}
|
||||
|
||||
// 获取用户群聊列表(带分组信息)
|
||||
export function getUserGroups() {
|
||||
return request.get<Array<{
|
||||
room_id: string
|
||||
room_type: string
|
||||
room_name: string
|
||||
room_avatar: string
|
||||
owner_id: string
|
||||
creator_id: string
|
||||
category: 'joined' | 'created' | 'managed'
|
||||
role: number
|
||||
last_message_time?: string
|
||||
last_message?: string
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}>>('/groups')
|
||||
}
|
||||
|
||||
// 获取群信息
|
||||
export function getGroup(roomId: string) {
|
||||
return request.get<GroupInfo>(`/groups/${roomId}`)
|
||||
}
|
||||
|
||||
// 获取群成员列表
|
||||
export function getGroupMembers(roomId: string, keyword?: string) {
|
||||
return request.get<GroupMember[]>(`/groups/${roomId}/members`, keyword ? { keyword } : undefined)
|
||||
}
|
||||
|
||||
// 邀请成员入群
|
||||
export function inviteGroupMembers(roomId: string, data: { member_ids: string[] }) {
|
||||
return request.post<void>(`/groups/${roomId}/members`, data)
|
||||
}
|
||||
|
||||
// 移除群成员
|
||||
export function removeGroupMember(roomId: string, userId: string) {
|
||||
return request.post<void>(`/groups/${roomId}/members/${userId}/remove`, {})
|
||||
}
|
||||
|
||||
// 修改群信息
|
||||
export function updateGroup(roomId: string, data: { name?: string; avatar?: string }) {
|
||||
return request.post<void>(`/groups/${roomId}/update`, data)
|
||||
}
|
||||
|
||||
// 调整成员角色
|
||||
export function updateMemberRole(roomId: string, userId: string, data: { role: number }) {
|
||||
return request.post<void>(`/groups/${roomId}/members/${userId}/role`, data)
|
||||
}
|
||||
|
||||
// 退出群聊
|
||||
export function quitGroup(roomId: string) {
|
||||
return request.post<void>(`/groups/${roomId}/quit`, {})
|
||||
}
|
||||
|
||||
// 解散群聊
|
||||
export function dissolveGroup(roomId: string) {
|
||||
return request.post<void>(`/groups/${roomId}/dissolve`, {})
|
||||
}
|
||||
|
||||
// 获取群公告
|
||||
export function getGroupAnnouncement(roomId: string) {
|
||||
return request.get<{ announcement: string }>(`/groups/${roomId}/announcement`)
|
||||
}
|
||||
|
||||
// 更新群公告
|
||||
export function updateGroupAnnouncement(roomId: string, announcement: string) {
|
||||
return request.post<void>(`/groups/${roomId}/announcement`, { announcement })
|
||||
}
|
||||
|
||||
// 获取群通知列表
|
||||
export function getGroupNotifications(params?: {
|
||||
page?: number
|
||||
page_size?: number
|
||||
is_read?: boolean
|
||||
}) {
|
||||
return request.get<{
|
||||
data: Array<{
|
||||
id: number
|
||||
room_id: string
|
||||
message_type: number
|
||||
content: string
|
||||
extra: string | object
|
||||
is_read?: boolean
|
||||
created_at: string
|
||||
}>
|
||||
total: number
|
||||
page: number
|
||||
page_size: number
|
||||
}>('/group-notifications', params)
|
||||
}
|
||||
|
||||
// 禁言成员
|
||||
export function muteGroupMember(roomId: string, userId: string, duration: number) {
|
||||
return request.post<void>(`/groups/${roomId}/members/${userId}/mute`, { duration })
|
||||
}
|
||||
|
||||
// 更新群设置
|
||||
export function updateGroupSettings(roomId: string, data: {
|
||||
name?: string
|
||||
avatar?: string
|
||||
description?: string
|
||||
invite_permission?: number
|
||||
}) {
|
||||
return request.post<void>(`/groups/${roomId}/settings`, data)
|
||||
}
|
||||
|
||||
// 获取群设置
|
||||
export function getGroupSettings(roomId: string) {
|
||||
return request.get<{
|
||||
name: string
|
||||
avatar: string
|
||||
description: string
|
||||
invite_permission: number
|
||||
created_at: string
|
||||
updated_at: string
|
||||
}>(`/groups/${roomId}/settings`)
|
||||
}
|
||||
25
src/api/modules/system.ts
Normal file
25
src/api/modules/system.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* 系统接口相关 API
|
||||
*/
|
||||
import request from '../request'
|
||||
import type { ICEServerConfig } from '@/types/api'
|
||||
|
||||
// 健康检查
|
||||
export function healthCheck() {
|
||||
return request.get<{ status: string; node: string; time: string }>('/health')
|
||||
}
|
||||
|
||||
// 获取ICE服务器配置
|
||||
export function getIceServers(userId: string) {
|
||||
return request.get<ICEServerConfig[]>('/ice-servers', { user_id: userId })
|
||||
}
|
||||
|
||||
// 检查用户在线状态
|
||||
export function checkUserOnline(userId: string) {
|
||||
return request.get<{ is_online: boolean }>('/check-user-online', { user_id: userId })
|
||||
}
|
||||
|
||||
// 绑定ClientID和UserID
|
||||
export function bindClient(data: { user_id: string; client_id: string }) {
|
||||
return request.post<void>('/bind', data)
|
||||
}
|
||||
33
src/api/modules/user.ts
Normal file
33
src/api/modules/user.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
/**
|
||||
* 用户管理相关 API
|
||||
*/
|
||||
import request from '../request'
|
||||
import type { User, PaginatedResponse } from '@/types/api'
|
||||
|
||||
// 获取当前用户信息
|
||||
export function getMyInfo() {
|
||||
return request.get<User>('/user/my-info')
|
||||
}
|
||||
|
||||
// 获取用户列表
|
||||
export function getUserList(page = 1, pageSize = 20) {
|
||||
return request.get<PaginatedResponse<User>>('/user/list', {
|
||||
page,
|
||||
page_size: pageSize
|
||||
})
|
||||
}
|
||||
|
||||
// 创建用户(管理员功能)
|
||||
export function createUser(data: Partial<User>) {
|
||||
return request.post<User>('/user/create', data)
|
||||
}
|
||||
|
||||
// 更新用户信息
|
||||
export function updateUser(data: { id: string; updates: Partial<User> }) {
|
||||
return request.post<User>('/user/update', data)
|
||||
}
|
||||
|
||||
// 删除用户(管理员功能)
|
||||
export function deleteUser(id: string) {
|
||||
return request.post<void>('/user/delete', { id })
|
||||
}
|
||||
264
src/api/request/index.ts
Normal file
264
src/api/request/index.ts
Normal file
@@ -0,0 +1,264 @@
|
||||
/**
|
||||
* UniApp 请求封装
|
||||
*/
|
||||
import type { ApiResponse, RequestConfig } from './types'
|
||||
import { storage } from '@/utils/storage'
|
||||
|
||||
// API 基础路径
|
||||
const BASE_URL = 'http://127.0.0.1:12080/api'
|
||||
|
||||
// 默认超时时间
|
||||
const TIMEOUT = 30000
|
||||
|
||||
/**
|
||||
* 获取存储的 Token
|
||||
*/
|
||||
function getToken(): string | null {
|
||||
return storage.getToken()
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取存储的 User ID
|
||||
*/
|
||||
function getUserId(): string | null {
|
||||
return storage.getUserId()
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示加载提示
|
||||
*/
|
||||
function showLoading() {
|
||||
uni.showLoading({
|
||||
title: '加载中...',
|
||||
mask: true
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 隐藏加载提示
|
||||
*/
|
||||
function hideLoading() {
|
||||
uni.hideLoading()
|
||||
}
|
||||
|
||||
/**
|
||||
* 显示错误提示
|
||||
*/
|
||||
function showError(message: string) {
|
||||
uni.showToast({
|
||||
title: message,
|
||||
icon: 'none',
|
||||
duration: 3000
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求函数
|
||||
*/
|
||||
function request<T = any>(config: RequestConfig): Promise<T> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const {
|
||||
url,
|
||||
method = 'GET',
|
||||
data,
|
||||
params,
|
||||
header = {},
|
||||
timeout = TIMEOUT,
|
||||
showLoading: needLoading = false,
|
||||
showError: needShowError = true
|
||||
} = config
|
||||
|
||||
// 构建完整 URL
|
||||
let fullUrl = BASE_URL + url
|
||||
|
||||
// 处理 GET 请求参数
|
||||
if (params && Object.keys(params).length > 0) {
|
||||
const queryString = Object.entries(params)
|
||||
.filter(([_, v]) => v !== undefined && v !== null)
|
||||
.map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(String(v))}`)
|
||||
.join('&')
|
||||
if (queryString) {
|
||||
fullUrl += (fullUrl.includes('?') ? '&' : '?') + queryString
|
||||
}
|
||||
}
|
||||
|
||||
// 设置请求头
|
||||
const headers: Record<string, string> = {
|
||||
'Content-Type': 'application/json',
|
||||
...header
|
||||
}
|
||||
|
||||
// 添加 Token
|
||||
const token = getToken()
|
||||
if (token) {
|
||||
headers['Authorization'] = `Bearer ${token}`
|
||||
}
|
||||
|
||||
// 添加 User ID
|
||||
const userId = getUserId()
|
||||
if (userId) {
|
||||
headers['X-User-ID'] = userId
|
||||
}
|
||||
|
||||
// 显示加载
|
||||
if (needLoading) {
|
||||
showLoading()
|
||||
}
|
||||
|
||||
// 发起请求
|
||||
uni.request({
|
||||
url: fullUrl,
|
||||
method,
|
||||
data,
|
||||
header: headers,
|
||||
timeout,
|
||||
success: (res) => {
|
||||
if (needLoading) {
|
||||
hideLoading()
|
||||
}
|
||||
|
||||
const response = res.data as ApiResponse<T>
|
||||
|
||||
// 检查 HTTP 状态码
|
||||
if (res.statusCode !== 200) {
|
||||
const errorMsg = `请求失败: ${res.statusCode}`
|
||||
if (needShowError) {
|
||||
showError(errorMsg)
|
||||
}
|
||||
reject(new Error(errorMsg))
|
||||
return
|
||||
}
|
||||
|
||||
// 检查业务状态码
|
||||
if (response.code !== 0) {
|
||||
const errorMsg = response.message || '请求失败'
|
||||
if (needShowError) {
|
||||
showError(errorMsg)
|
||||
}
|
||||
reject(new Error(errorMsg))
|
||||
return
|
||||
}
|
||||
|
||||
// 返回结果数据
|
||||
resolve(response.result)
|
||||
},
|
||||
fail: (err) => {
|
||||
if (needLoading) {
|
||||
hideLoading()
|
||||
}
|
||||
|
||||
const errorMsg = err.errMsg || '网络请求失败'
|
||||
if (needShowError) {
|
||||
showError(errorMsg)
|
||||
}
|
||||
reject(new Error(errorMsg))
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* GET 请求
|
||||
*/
|
||||
function get<T = any>(url: string, params?: Record<string, any>, config?: Partial<RequestConfig>): Promise<T> {
|
||||
return request<T>({
|
||||
url,
|
||||
method: 'GET',
|
||||
params,
|
||||
...config
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* POST 请求
|
||||
*/
|
||||
function post<T = any>(url: string, data?: any, config?: Partial<RequestConfig>): Promise<T> {
|
||||
return request<T>({
|
||||
url,
|
||||
method: 'POST',
|
||||
data,
|
||||
...config
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* PUT 请求
|
||||
*/
|
||||
function put<T = any>(url: string, data?: any, config?: Partial<RequestConfig>): Promise<T> {
|
||||
return request<T>({
|
||||
url,
|
||||
method: 'PUT',
|
||||
data,
|
||||
...config
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* DELETE 请求
|
||||
*/
|
||||
function del<T = any>(url: string, data?: any, config?: Partial<RequestConfig>): Promise<T> {
|
||||
return request<T>({
|
||||
url,
|
||||
method: 'DELETE',
|
||||
data,
|
||||
...config
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传文件
|
||||
*/
|
||||
function upload<T = any>(url: string, filePath: string, name = 'file', formData?: Record<string, any>): Promise<T> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const headers: Record<string, string> = {}
|
||||
|
||||
const token = getToken()
|
||||
if (token) {
|
||||
headers['Authorization'] = `Bearer ${token}`
|
||||
}
|
||||
|
||||
const userId = getUserId()
|
||||
if (userId) {
|
||||
headers['X-User-ID'] = userId
|
||||
}
|
||||
|
||||
uni.uploadFile({
|
||||
url: BASE_URL + url,
|
||||
filePath,
|
||||
name,
|
||||
formData,
|
||||
header: headers,
|
||||
success: (res) => {
|
||||
if (res.statusCode !== 200) {
|
||||
reject(new Error(`上传失败: ${res.statusCode}`))
|
||||
return
|
||||
}
|
||||
|
||||
try {
|
||||
const response = JSON.parse(res.data) as ApiResponse<T>
|
||||
if (response.code !== 0) {
|
||||
reject(new Error(response.message || '上传失败'))
|
||||
return
|
||||
}
|
||||
resolve(response.result)
|
||||
} catch {
|
||||
reject(new Error('解析响应失败'))
|
||||
}
|
||||
},
|
||||
fail: (err) => {
|
||||
reject(new Error(err.errMsg || '上传失败'))
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export default {
|
||||
request,
|
||||
get,
|
||||
post,
|
||||
put,
|
||||
delete: del,
|
||||
upload
|
||||
}
|
||||
|
||||
export { get, post, put, del as delete, upload, request }
|
||||
30
src/api/request/types.ts
Normal file
30
src/api/request/types.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
/**
|
||||
* API 请求/响应类型定义
|
||||
*/
|
||||
|
||||
// 统一响应格式
|
||||
export interface ApiResponse<T = any> {
|
||||
code: number
|
||||
message: string
|
||||
result: T
|
||||
}
|
||||
|
||||
// 分页响应
|
||||
export interface PaginatedResponse<T> {
|
||||
data: T[]
|
||||
total: number
|
||||
page: number
|
||||
size: number
|
||||
}
|
||||
|
||||
// 请求配置
|
||||
export interface RequestConfig {
|
||||
url: string
|
||||
method?: 'GET' | 'POST' | 'PUT' | 'DELETE'
|
||||
data?: any
|
||||
params?: Record<string, any>
|
||||
header?: Record<string, string>
|
||||
timeout?: number
|
||||
showLoading?: boolean
|
||||
showError?: boolean
|
||||
}
|
||||
243
src/api/websocket/index.ts
Normal file
243
src/api/websocket/index.ts
Normal file
@@ -0,0 +1,243 @@
|
||||
/**
|
||||
* WebSocket 管理器 - UniApp 适配版
|
||||
*/
|
||||
import type { ChatMessage } from '@/types/api'
|
||||
import type { MomentNotifPayload } from '@/types/moment'
|
||||
|
||||
export interface WebSocketMessage {
|
||||
request_type?: string
|
||||
clientId?: string
|
||||
data?: ChatMessage | MomentNotifPayload
|
||||
}
|
||||
|
||||
export type MessageHandler = (message: ChatMessage) => void
|
||||
export type SignalHandler = (message: ChatMessage) => void
|
||||
export type MomentNotifHandler = (payload: MomentNotifPayload) => void
|
||||
|
||||
class WebSocketManager {
|
||||
private socketTask: UniApp.SocketTask | null = null
|
||||
private clientId: string | null = null
|
||||
private userId: string | null = null
|
||||
private messageHandlers: MessageHandler[] = []
|
||||
private signalHandlers: SignalHandler[] = []
|
||||
private momentNotifHandlers: MomentNotifHandler[] = []
|
||||
private reconnectAttempts = 0
|
||||
private maxReconnectAttempts = 5
|
||||
private reconnectDelay = 3000
|
||||
private recentMomentNotifs: Set<string> = new Set()
|
||||
private notifCacheTimeout = 5000
|
||||
|
||||
/**
|
||||
* 连接 WebSocket
|
||||
*/
|
||||
connect(userId: string): Promise<void> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (this.socketTask) {
|
||||
resolve()
|
||||
return
|
||||
}
|
||||
|
||||
this.userId = userId
|
||||
// TODO: 替换为实际的 WebSocket 地址
|
||||
const wsUrl = `ws://localhost:12080/ws?user_id=${userId}`
|
||||
|
||||
this.socketTask = uni.connectSocket({
|
||||
url: wsUrl,
|
||||
success: () => {
|
||||
console.log('WebSocket connecting...')
|
||||
},
|
||||
fail: (err) => {
|
||||
console.error('WebSocket connect failed:', err)
|
||||
reject(err)
|
||||
}
|
||||
})
|
||||
|
||||
this.socketTask.onOpen(() => {
|
||||
console.log('✅ WebSocket connected')
|
||||
this.reconnectAttempts = 0
|
||||
resolve()
|
||||
})
|
||||
|
||||
this.socketTask.onMessage((res) => {
|
||||
try {
|
||||
const data = (res.data as string).trim()
|
||||
const lines = data.split('\n').filter((line: string) => line.trim())
|
||||
|
||||
for (const line of lines) {
|
||||
try {
|
||||
const payload: WebSocketMessage = JSON.parse(line)
|
||||
|
||||
// 接收 clientId
|
||||
if (payload.clientId) {
|
||||
this.clientId = payload.clientId
|
||||
}
|
||||
|
||||
// 处理接收消息
|
||||
if (payload.request_type === 'receive_message' && payload.data) {
|
||||
this.handleMessage(payload.data as ChatMessage)
|
||||
}
|
||||
|
||||
// 处理朋友圈通知
|
||||
if (payload.request_type === 'moment_notification' && payload.data) {
|
||||
this.handleMomentNotification(payload.data as MomentNotifPayload)
|
||||
}
|
||||
} catch {
|
||||
// 忽略单行解析错误
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('WebSocket message parse error:', error)
|
||||
}
|
||||
})
|
||||
|
||||
this.socketTask.onError((error) => {
|
||||
console.error('WebSocket error:', error)
|
||||
reject(error)
|
||||
})
|
||||
|
||||
this.socketTask.onClose(() => {
|
||||
console.log('WebSocket closed')
|
||||
this.socketTask = null
|
||||
this.attemptReconnect()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 断开连接
|
||||
*/
|
||||
disconnect() {
|
||||
if (this.socketTask) {
|
||||
this.socketTask.close({})
|
||||
this.socketTask = null
|
||||
}
|
||||
this.clientId = null
|
||||
this.userId = null
|
||||
this.messageHandlers = []
|
||||
this.signalHandlers = []
|
||||
this.momentNotifHandlers = []
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取 ClientID
|
||||
*/
|
||||
getClientId(): string | null {
|
||||
return this.clientId
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加普通消息处理器
|
||||
*/
|
||||
onMessage(handler: MessageHandler) {
|
||||
if (!this.messageHandlers.includes(handler)) {
|
||||
this.messageHandlers.push(handler)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除普通消息处理器
|
||||
*/
|
||||
offMessage(handler: MessageHandler) {
|
||||
const index = this.messageHandlers.indexOf(handler)
|
||||
if (index > -1) {
|
||||
this.messageHandlers.splice(index, 1)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加信令处理器 (WebRTC用)
|
||||
*/
|
||||
onSignal(handler: SignalHandler) {
|
||||
if (!this.signalHandlers.includes(handler)) {
|
||||
this.signalHandlers.push(handler)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除信令处理器
|
||||
*/
|
||||
offSignal(handler: SignalHandler) {
|
||||
const index = this.signalHandlers.indexOf(handler)
|
||||
if (index > -1) {
|
||||
this.signalHandlers.splice(index, 1)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 添加朋友圈通知处理器
|
||||
*/
|
||||
onMomentNotification(handler: MomentNotifHandler) {
|
||||
if (!this.momentNotifHandlers.includes(handler)) {
|
||||
this.momentNotifHandlers.push(handler)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除朋友圈通知处理器
|
||||
*/
|
||||
offMomentNotification(handler: MomentNotifHandler) {
|
||||
const index = this.momentNotifHandlers.indexOf(handler)
|
||||
if (index > -1) {
|
||||
this.momentNotifHandlers.splice(index, 1)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 内部处理朋友圈通知(带去重)
|
||||
*/
|
||||
private handleMomentNotification(payload: MomentNotifPayload) {
|
||||
const notifKey = `${payload.moment_id}_${payload.type}_${payload.from_user?.id || ''}_${payload.comment_id || ''}`
|
||||
|
||||
if (this.recentMomentNotifs.has(notifKey)) {
|
||||
console.log('🔄 跳过重复朋友圈通知:', notifKey)
|
||||
return
|
||||
}
|
||||
|
||||
this.recentMomentNotifs.add(notifKey)
|
||||
|
||||
setTimeout(() => {
|
||||
this.recentMomentNotifs.delete(notifKey)
|
||||
}, this.notifCacheTimeout)
|
||||
|
||||
this.momentNotifHandlers.forEach((handler) => handler(payload))
|
||||
}
|
||||
|
||||
/**
|
||||
* 内部处理接收到的消息
|
||||
*/
|
||||
private handleMessage(message: ChatMessage) {
|
||||
// 信令消息(message_type = 6)- 路由到 signalHandlers
|
||||
if (message.message_type === 6) {
|
||||
this.signalHandlers.forEach((handler) => handler(message))
|
||||
return
|
||||
}
|
||||
|
||||
// 普通消息 - 路由到 messageHandlers
|
||||
this.messageHandlers.forEach((handler) => handler(message))
|
||||
}
|
||||
|
||||
/**
|
||||
* 尝试重连
|
||||
*/
|
||||
private attemptReconnect() {
|
||||
if (this.reconnectAttempts >= this.maxReconnectAttempts) {
|
||||
console.log('WebSocket max reconnect attempts reached')
|
||||
return
|
||||
}
|
||||
|
||||
if (!this.userId) {
|
||||
return
|
||||
}
|
||||
|
||||
this.reconnectAttempts++
|
||||
console.log(`WebSocket reconnecting... (${this.reconnectAttempts}/${this.maxReconnectAttempts})`)
|
||||
|
||||
setTimeout(() => {
|
||||
if (this.userId) {
|
||||
this.connect(this.userId).catch(console.error)
|
||||
}
|
||||
}, this.reconnectDelay)
|
||||
}
|
||||
}
|
||||
|
||||
export const wsManager = new WebSocketManager()
|
||||
Reference in New Issue
Block a user