import axios from 'axios' import { ElMessage } from 'element-plus' import router from '@/router' import { isJwtExpired } from '@/utils/jwt' const imClient = axios.create({ baseURL: import.meta.env.VITE_IM_API_BASE_URL ?? '', timeout: 15000, }) if (import.meta.env.DEV) { imClient.interceptors.request.use((config) => { console.debug('[tenant-platform][IM] request', { method: config.method?.toUpperCase(), url: config.baseURL ? `${config.baseURL}${config.url ?? ''}` : config.url, params: config.params, }) return config }) imClient.interceptors.response.use((res) => { console.debug('[tenant-platform][IM] response', { status: res.status, url: res.config.url, }) return res }) } imClient.interceptors.request.use((config) => { const token = localStorage.getItem('token') if (token && !isJwtExpired(token)) { config.headers.Authorization = `Bearer ${token}` } else if (token && isJwtExpired(token)) { localStorage.removeItem('token') if (router.currentRoute.value.path !== '/login') { router.push('/login?reason=' + encodeURIComponent('登录已失效,请重新登录')) } return Promise.reject(new Error('登录已失效,请重新登录')) } return config }) imClient.interceptors.response.use( (res) => res, (error) => { const status = error.response?.status if (status === 401) { localStorage.removeItem('token') if (router.currentRoute.value.path !== '/login') { router.push('/login') } ElMessage.error('登录已失效,请重新登录') return Promise.reject(error) } if (status === 403) { localStorage.removeItem('token') if (router.currentRoute.value.path !== '/login') { router.push('/login?reason=' + encodeURIComponent('登录已失效,请重新登录')) } ElMessage.error(error.response?.data?.message ?? '登录已失效,请重新登录') return Promise.reject(error) } const msg = error.response?.data?.message ?? 'IM 请求失败' ElMessage.error(msg) return Promise.reject(error) }, ) export interface ImUser { id: string appKey: string userId: string nickname: string avatar?: string admin?: boolean status: 'ACTIVE' | 'BANNED' gender: 'UNKNOWN' | 'MALE' | 'FEMALE' createdAt: number } export interface ImProfile { id?: string appKey?: string userId: string nickname?: string | null avatar?: string | null gender?: 'UNKNOWN' | 'MALE' | 'FEMALE' | string | null status?: 'ACTIVE' | 'BANNED' | string | null createdAt?: number | null } export interface ImGroup { id: string appKey: string name: string creatorId: string groupType?: string | null memberIds: string adminIds: string announcement?: string | null createdAt: number } export interface ImMessage { id: string appKey: string fromUserId: string toId: string chatType: 'SINGLE' | 'GROUP' msgType: string content: string status: string mentionedUserIds?: string | null groupReadCount?: number | null createdAt: number } export interface PagedResult { content: T[] totalElements: number totalPages: number size: number number: number } export interface ImStats { totalMessages: number totalUsers: number totalGroups: number todayMessages: number } export interface OperationLog { id: string appKey: string operatorId: string action: string resourceType: string resourceId?: string | null detail?: string | null createdAt: number } export interface KeywordFilter { id: string appKey: string pattern: string replacement?: string | null action: 'REPLACE' | 'BLOCK' enabled: boolean createdAt: number } export interface GlobalMute { id: string appKey: string enabled: boolean createdAt: number updatedAt: number } export interface WebhookConfig { id: string appKey: string url: string secret?: string | null enabled: boolean createdAt: number consecutiveFailures?: number lastFailureAt?: number | null } export interface WebhookConfigForm { url: string secret?: string enabled?: boolean } export interface WebhookDelivery { id: string appKey: string callbackId: string callbackEvent: string url: string httpStatus: number responseBody?: string | null errorMessage?: string | null attempt: number success: boolean createdAt: number } export interface WebhookAlert { id: string appKey: string webhookId: string webhookUrl: string alertType: string description?: string | null acknowledged: boolean createdAt: number acknowledgedAt?: number | null } export interface GroupJoinRequest { id: string appKey: string groupId: string requesterId: string remark?: string | null status: 'PENDING' | 'ACCEPTED' | 'REJECTED' createdAt: number reviewedAt?: number | null } export const imAdminApi = { listUsers(appKey: string, page = 0, size = 20) { return imClient.get<{ data: PagedResult }>( '/api/im/admin/users', { params: { appKey, page, size } }, ) }, updateUserStatus(appKey: string, userId: string, status: 'ACTIVE' | 'BANNED') { return imClient.put(`/api/im/admin/users/${encodeURIComponent(userId)}/status`, { status }, { params: { appKey } }) }, listGroups(appKey: string) { return imClient.get<{ data: ImGroup[] }>('/api/im/admin/groups', { params: { appKey } }) }, getStats(appKey: string) { return imClient.get<{ data: ImStats }>('/api/im/admin/stats', { params: { appKey } }) }, getOperationLogs(appKey: string, page = 0, size = 20) { return imClient.get<{ data: PagedResult }>('/api/im/admin/operation-logs', { params: { appKey, page, size }, }) }, listWebhooks(appKey: string) { return imClient.get<{ data: WebhookConfig[] }>('/api/im/admin/webhooks', { params: { appKey }, }) }, createWebhook(appKey: string, form: WebhookConfigForm) { return imClient.post<{ data: WebhookConfig }>('/api/im/admin/webhooks', form, { params: { appKey }, }) }, updateUser( appKey: string, userId: string, form: { nickname?: string; avatar?: string; gender?: string; status?: string; admin?: boolean }, ) { return imClient.put<{ data: ImUser }>( `/api/im/admin/users/${encodeURIComponent(userId)}`, form, { params: { appKey } }, ) }, generateUserSig( appKey: string, userId: string, form?: { expireSeconds?: number; userBuf?: string }, ) { return imClient.post<{ data: { appKey: string; userId: string; userSig: string; issuedAt: number; expiresAt: number; userBuf?: string } }>( `/api/im/admin/users/${encodeURIComponent(userId)}/usersig`, form ?? {}, { params: { appKey } }, ) }, verifyUserSig( appKey: string, userId: string, userSig: string, ) { return imClient.post<{ data: { valid: boolean; appKey: string; userId: string; issuedAt: number; expiresAt: number; userBuf?: string } }>( `/api/im/admin/users/${encodeURIComponent(userId)}/usersig/verify`, { userSig }, { params: { appKey } }, ) }, updateWebhook(appKey: string, webhookId: string, form: WebhookConfigForm) { return imClient.put<{ data: WebhookConfig }>(`/api/im/admin/webhooks/${encodeURIComponent(webhookId)}`, form, { params: { appKey }, }) }, deleteWebhook(appKey: string, webhookId: string) { return imClient.delete<{ data: null }>(`/api/im/admin/webhooks/${encodeURIComponent(webhookId)}`, { params: { appKey }, }) }, listKeywordFilters(appKey: string) { return imClient.get<{ data: KeywordFilter[] }>('/api/im/admin/keyword-filters', { params: { appKey }, }) }, createKeywordFilter( appKey: string, form: { pattern: string; replacement?: string; action: 'REPLACE' | 'BLOCK'; enabled: boolean }, ) { return imClient.post<{ data: KeywordFilter }>('/api/im/admin/keyword-filters', form, { params: { appKey }, }) }, updateKeywordFilter( appKey: string, filterId: string, form: { pattern: string; replacement?: string; action: 'REPLACE' | 'BLOCK'; enabled: boolean }, ) { return imClient.put<{ data: KeywordFilter }>(`/api/im/admin/keyword-filters/${encodeURIComponent(filterId)}`, form, { params: { appKey }, }) }, deleteKeywordFilter(appKey: string, filterId: string) { return imClient.delete<{ data: null }>(`/api/im/admin/keyword-filters/${encodeURIComponent(filterId)}`, { params: { appKey }, }) }, getGlobalMute(appKey: string) { return imClient.get<{ data: GlobalMute }>('/api/im/admin/global-mute', { params: { appKey }, }) }, setGlobalMute(appKey: string, enabled: boolean) { return imClient.put<{ data: GlobalMute }>('/api/im/admin/global-mute', null, { params: { appKey, enabled }, }) }, listGroupMembers(appKey: string, groupId: string) { return imClient.get<{ data: ImUser[] }>( `/api/im/admin/groups/${encodeURIComponent(groupId)}/members`, { params: { appKey } }, ) }, searchGroupMembers(appKey: string, groupId: string, keyword: string, size = 20) { return imClient.get<{ data: ImUser[] }>( `/api/im/admin/groups/${encodeURIComponent(groupId)}/members/search`, { params: { appKey, keyword, size } }, ) }, addGroupMember(appKey: string, groupId: string, userId: string) { return imClient.post<{ data: ImGroup }>( `/api/im/admin/groups/${encodeURIComponent(groupId)}/members`, { userId }, { params: { appKey } }, ) }, removeGroupMember(appKey: string, groupId: string, userId: string) { return imClient.delete<{ data: ImGroup }>( `/api/im/admin/groups/${encodeURIComponent(groupId)}/members/${encodeURIComponent(userId)}`, { params: { appKey } }, ) }, setGroupRole(appKey: string, groupId: string, userId: string, role: 'ADMIN' | 'MEMBER') { return imClient.post<{ data: ImGroup }>( `/api/im/admin/groups/${encodeURIComponent(groupId)}/roles`, { userId, role }, { params: { appKey } }, ) }, muteGroupMember(appKey: string, groupId: string, userId: string, minutes: number) { return imClient.post<{ data: ImGroup }>( `/api/im/admin/groups/${encodeURIComponent(groupId)}/mute`, { userId, minutes }, { params: { appKey } }, ) }, listGroupJoinRequests(appKey: string, groupId: string) { return imClient.get<{ data: GroupJoinRequest[] }>( `/api/im/admin/groups/${encodeURIComponent(groupId)}/join-requests`, { params: { appKey } }, ) }, sendGroupJoinRequest(appKey: string, groupId: string, remark?: string) { return imClient.post<{ data: GroupJoinRequest }>( `/api/im/groups/${encodeURIComponent(groupId)}/join-requests`, null, { params: { appKey, ...(remark ? { remark } : {}), }, }, ) }, acceptGroupJoinRequest(appKey: string, groupId: string, requestId: string) { return imClient.post<{ data: GroupJoinRequest }>( `/api/im/admin/groups/${encodeURIComponent(groupId)}/join-requests/${encodeURIComponent(requestId)}/accept`, null, { params: { appKey } }, ) }, rejectGroupJoinRequest(appKey: string, groupId: string, requestId: string) { return imClient.post<{ data: GroupJoinRequest }>( `/api/im/admin/groups/${encodeURIComponent(groupId)}/join-requests/${encodeURIComponent(requestId)}/reject`, null, { params: { appKey } }, ) }, getMessages( appKey: string, userA: string, userB: string, page = 0, size = 20, filters?: { msgType?: string keyword?: string startTime?: string endTime?: string }, ) { return imClient.get<{ data: PagedResult }>('/api/im/admin/messages', { params: { appKey, userA, userB, page, size, ...(filters?.msgType ? { msgType: filters.msgType } : {}), ...(filters?.keyword ? { keyword: filters.keyword } : {}), ...(filters?.startTime ? { startTime: filters.startTime } : {}), ...(filters?.endTime ? { endTime: filters.endTime } : {}), }, }) }, revokeMessage(appKey: string, messageId: string) { return imClient.post<{ data: ImMessage }>( `/api/im/admin/messages/${encodeURIComponent(messageId)}/revoke`, {}, { params: { appKey } }, ) }, dismissGroup(appKey: string, groupId: string) { return imClient.delete<{ data: null }>(`/api/im/admin/groups/${encodeURIComponent(groupId)}`, { params: { appKey } }) }, registerUser( appKey: string, userId: string, nickname?: string, avatar?: string, gender?: string, status?: string, admin?: boolean, ) { return imClient.post<{ data: ImUser }>( '/api/im/admin/users', { userId, nickname, avatar, ...(gender ? { gender } : {}), ...(status ? { status } : {}), ...(admin !== undefined ? { admin } : {}), }, { params: { appKey } }, ) }, createGroup( appKey: string, name: string, creatorId: string, memberIds: string[], groupType?: string, announcement?: string, ) { return imClient.post<{ data: ImGroup }>( '/api/im/admin/groups', { name, creatorId, memberIds, ...(groupType ? { groupType } : {}), ...(announcement ? { announcement } : {}), }, { params: { appKey } }, ) }, updateGroup( appKey: string, groupId: string, form: { name?: string; groupType?: string; announcement?: string }, ) { return imClient.put<{ data: ImGroup }>( `/api/im/admin/groups/${encodeURIComponent(groupId)}`, form, { params: { appKey } }, ) }, searchUsers(appKey: string, keyword: string, size = 20) { return imClient.get<{ data: ImUser[] }>( '/api/im/admin/users/search', { params: { appKey, keyword, size } }, ) }, searchMessages( appKey: string, filters: { keyword?: string chatType?: string msgType?: string startTime?: string endTime?: string page?: number size?: number } = {}, ) { return imClient.get<{ data: PagedResult }>('/api/im/admin/messages/search', { params: { appKey, ...(filters.keyword ? { keyword: filters.keyword } : {}), ...(filters.chatType ? { chatType: filters.chatType } : {}), ...(filters.msgType ? { msgType: filters.msgType } : {}), ...(filters.startTime ? { startTime: filters.startTime } : {}), ...(filters.endTime ? { endTime: filters.endTime } : {}), page: filters.page ?? 0, size: filters.size ?? 20, }, }) }, getProfile(appKey: string, userId: string) { return imClient.get<{ data: ImProfile }>( `/api/im/accounts/${encodeURIComponent(userId)}`, { params: { appKey } }, ) }, updateProfile(appKey: string, userId: string, nickname?: string, avatar?: string, gender?: string) { return imClient.put<{ data: ImProfile }>( `/api/im/accounts/${encodeURIComponent(userId)}`, {}, { params: { appKey, ...(nickname ? { nickname } : {}), ...(avatar ? { avatar } : {}), ...(gender ? { gender } : {}), }, }, ) }, transferGroupOwner(appKey: string, groupId: string, newOwnerId: string) { return imClient.post<{ data: ImGroup }>( `/api/im/admin/groups/${encodeURIComponent(groupId)}/owner`, { newOwnerId }, { params: { appKey } }, ) }, updateGroupAttributes(appKey: string, groupId: string, attributes: Record) { return imClient.put<{ data: ImGroup }>( `/api/im/admin/groups/${encodeURIComponent(groupId)}/attributes`, { attributes }, { params: { appKey } }, ) }, removeGroupAttributes(appKey: string, groupId: string, keys: string[]) { return imClient.post<{ data: ImGroup }>( `/api/im/admin/groups/${encodeURIComponent(groupId)}/attributes/delete`, { keys }, { params: { appKey } }, ) }, getGroupReadReceipts(appKey: string, groupId: string, messageIds?: string[]) { return imClient.post<{ data: Array<{ messageId: string; readCount: number; readUserIds: string[] }> }>( `/api/im/admin/groups/${encodeURIComponent(groupId)}/read-receipts`, { messageIds }, { params: { appKey } }, ) }, queryUserState(appKey: string, userIds: string[]) { return imClient.get<{ data: Record }>( '/api/im/admin/users/state', { params: { userIds: userIds.join(',') } }, ) }, listWebhookDeliveries( appKey: string, params: { callbackEvent?: string; success?: boolean; page?: number; size?: number } = {}, ) { return imClient.get<{ data: PagedResult }>('/api/im/admin/webhook-deliveries', { params: { appKey, ...params }, }) }, listWebhookAlerts( appKey: string, params: { acknowledged?: boolean; page?: number; size?: number } = {}, ) { return imClient.get<{ data: PagedResult }>('/api/im/admin/webhook-alerts', { params: { appKey, ...params }, }) }, acknowledgeWebhookAlert(appKey: string, alertId: string) { return imClient.post<{ data: WebhookAlert }>( `/api/im/admin/webhook-alerts/${encodeURIComponent(alertId)}/acknowledge`, null, { params: { appKey } }, ) }, getWebhookHealth(appKey: string, webhookId: string) { return imClient.get<{ data: { webhookId: string url: string enabled: boolean consecutiveFailures: number lastFailureAt: number | null unacknowledgedAlerts: number } }>(`/api/im/admin/webhooks/${encodeURIComponent(webhookId)}/health`, { params: { appKey } }) }, }