Files
YG_FT/frontend/src/api/request.ts
wuyongtao 6f0e82f351 feat: 平台治理与权限体系完善,存储进度/GPU预留/审批中心与日志整合
- 平台治理: 租户用户权限层次、资源ACL、审批中心与审批模板、访问申请
- 存储: MinIO 存储进度迁移、对象存储安全加固与测试
- 计算: GPU 资源预留、compute 轮询与同步增强
- 权限: permission v2 迁移、权限安全验收测试
- 日志: 后端运行日志中文说明、操作日志整合
- 数据处理/评测: 数据转换与模型评测优化

Co-Authored-By: Claude <noreply@anthropic.com>
2026-08-21 09:49:48 +08:00

145 lines
4.6 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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