XuqmGroup-Web/tenant-platform/src/api/im.ts
XuqmGroup 09891bf46e docs(deploy): 添加完整的部署文档和配置示例
- 新增 compose.production.yaml 和 compose.production.server.yaml 部署配置
- 添加 nginx.dev.xuqinmin.com.conf 和 nginx.sentry.xuqinmin.com.conf 反向代理配置
- 创建详细的部署指南文档 deploy/README.md,涵盖架构设计和部署步骤
- 添加前端访问文档 web/README.md,包含线上地址和接口说明
- 补充平台文档总览 README.md,整合各模块文档入口
- 配置多服务容器化部署,包括 tenant-service、im-service、push-service 等
- 设置外部数据库和 Redis 连接配置,确保服务间正确通信
- 配置 WebSocket 和 API 路由转发规则,支持实时通信和版本更新服务
2026-05-09 14:53:43 +08:00

654 行
18 KiB
TypeScript

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<T> {
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<ImUser> }>(
'/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<OperationLog> }>('/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<ImMessage> }>('/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<ImMessage> }>('/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<string, unknown>) {
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<string, { online: boolean; lastSeenAt: number }> }>(
'/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<WebhookDelivery> }>('/api/im/admin/webhook-deliveries', {
params: { appKey, ...params },
})
},
listWebhookAlerts(
appKey: string,
params: { acknowledged?: boolean; page?: number; size?: number } = {},
) {
return imClient.get<{ data: PagedResult<WebhookAlert> }>('/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 } })
},
}