- 平台治理: 租户用户权限层次、资源ACL、审批中心与审批模板、访问申请 - 存储: MinIO 存储进度迁移、对象存储安全加固与测试 - 计算: GPU 资源预留、compute 轮询与同步增强 - 权限: permission v2 迁移、权限安全验收测试 - 日志: 后端运行日志中文说明、操作日志整合 - 数据处理/评测: 数据转换与模型评测优化 Co-Authored-By: Claude <noreply@anthropic.com>
145 lines
4.6 KiB
TypeScript
145 lines
4.6 KiB
TypeScript
import axios, { type AxiosInstance, type AxiosRequestConfig } from 'axios'
|
||
import { ElMessage } from 'element-plus'
|
||
|
||
/**
|
||
* 用户操作分布:哪些模块路径算"业务操作"(用于看板统计)
|
||
* 请求命中这些路径时,会自动调用 record_visit 记录一次(同一模块 60 秒内去重)
|
||
*/
|
||
const VISIT_TRACKED_PREFIXES: Array<[string, string]> = [
|
||
['/fine-tune', 'fine-tune'],
|
||
['/model-eval', 'model-eval'],
|
||
['/model-inference', 'model-inference'],
|
||
['/model-compare', 'model-inference'],
|
||
['/data-process', 'data-process'],
|
||
['/data-convert', 'data-convert'],
|
||
['/model-manage', 'model-manage'],
|
||
['/dataset-manage', 'dataset'],
|
||
]
|
||
|
||
function trackVisit(url: string | undefined) {
|
||
if (!url) return
|
||
for (const [prefix, module] of VISIT_TRACKED_PREFIXES) {
|
||
if (url.includes(prefix)) {
|
||
const key = `visit:${module}`
|
||
const last = Number(sessionStorage.getItem(key) || 0)
|
||
if (Date.now() - last < 60000) return // 60 秒内去重
|
||
sessionStorage.setItem(key, String(Date.now()))
|
||
// fire-and-forget 调用后端记录接口
|
||
import('./modules/audit-visit').then(({ recordModuleVisit }) => {
|
||
recordModuleVisit(module, url).catch(() => { /* ignore */ })
|
||
}).catch(() => { /* ignore */ })
|
||
return
|
||
}
|
||
}
|
||
}
|
||
|
||
/**
|
||
* 后端统一响应格式
|
||
* code === 0 表示成功,data 为业务数据
|
||
*/
|
||
export interface ApiResult<T = any> {
|
||
code: number
|
||
message?: string
|
||
data: T
|
||
}
|
||
|
||
const service: AxiosInstance = axios.create({
|
||
// Use a relative path; Vite proxies /modelTF to http://localhost:17861 in local development.
|
||
baseURL: '/modelTF',
|
||
timeout: 120000,
|
||
})
|
||
|
||
/**
|
||
* 从 localStorage 取当前用户 token(登录时后端返回 platform-token-{user_id})。
|
||
* 后端鉴权中间件依赖此 header 解析当前用户身份。
|
||
*/
|
||
function getAuthToken(): string | null {
|
||
const USER_STORAGE_KEY = 'currentUser'
|
||
const raw = localStorage.getItem(USER_STORAGE_KEY)
|
||
if (raw) {
|
||
try {
|
||
const user = JSON.parse(raw)
|
||
// 后端 login 返回的 token 格式为 platform-token-{user.id}
|
||
if (user?.id) return `platform-token-${user.id}`
|
||
} catch {
|
||
/* ignore */
|
||
}
|
||
}
|
||
// 兼容改造前 admin 会话
|
||
if (localStorage.getItem('username') === 'admin') return 'platform-token-admin'
|
||
return null
|
||
}
|
||
|
||
function getActiveTenantId(): string | null {
|
||
return localStorage.getItem('activeTenantId')
|
||
}
|
||
|
||
/** Headers shared by Axios and raw fetch requests such as SSE streaming. */
|
||
export function getAuthHeaders(): Record<string, string> {
|
||
const headers: Record<string, string> = {}
|
||
const token = getAuthToken()
|
||
if (token) headers.Authorization = `Bearer ${token}`
|
||
const tenantId = getActiveTenantId()
|
||
if (tenantId) headers['X-Tenant-ID'] = tenantId
|
||
return headers
|
||
}
|
||
|
||
// 请求拦截器:注入 Authorization header
|
||
service.interceptors.request.use(
|
||
(config) => {
|
||
const authHeaders = getAuthHeaders()
|
||
if (Object.keys(authHeaders).length) {
|
||
config.headers = config.headers || {}
|
||
Object.assign(config.headers, authHeaders)
|
||
}
|
||
return config
|
||
},
|
||
(error) => Promise.reject(error),
|
||
)
|
||
|
||
// 响应拦截器:统一解包 { code, data, message }
|
||
service.interceptors.response.use(
|
||
(response) => {
|
||
const res = response.data as ApiResult
|
||
// 二进制流等非 JSON 响应直接返回
|
||
if (response.config.responseType === 'blob' || response.config.responseType === 'arraybuffer') {
|
||
return response
|
||
}
|
||
if (res.code === 0) {
|
||
return res.data
|
||
}
|
||
// 业务错误
|
||
const message = res.message || '请求失败'
|
||
ElMessage.error(message)
|
||
return Promise.reject(new Error(message))
|
||
},
|
||
(error) => {
|
||
const detail = error.response?.data?.detail
|
||
const message = detail?.message || error.response?.data?.message || detail || error.message || '网络异常'
|
||
ElMessage.error(message)
|
||
return Promise.reject(error)
|
||
},
|
||
)
|
||
|
||
/** GET 请求,返回已解包的 data */
|
||
export function get<T = any>(url: string, params?: any, config?: AxiosRequestConfig): Promise<T> {
|
||
return service.get(url, { params, ...config }) as unknown as Promise<T>
|
||
}
|
||
|
||
/** POST 请求 */
|
||
export function post<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
|
||
return service.post(url, data, config) as unknown as Promise<T>
|
||
}
|
||
|
||
/** PUT 请求 */
|
||
export function put<T = any>(url: string, data?: any, config?: AxiosRequestConfig): Promise<T> {
|
||
return service.put(url, data, config) as unknown as Promise<T>
|
||
}
|
||
|
||
/** DELETE 请求 */
|
||
export function del<T = any>(url: string, params?: any, config?: AxiosRequestConfig): Promise<T> {
|
||
return service.delete(url, { params, ...config }) as unknown as Promise<T>
|
||
}
|
||
|
||
export default service
|