- 扩展 ToolConfig 配置类型,新增 external_api 类型
- 实现接口注册表,包含 90+ 睿美云开放接口定义
- 实现 TPOS SHA256WithRSA 签名鉴权
- 实现睿美云 API 客户端,支持多租户配置
- 新增代理路由 /api/ruimeiyun/call/{api_name}
- 支持接口权限控制和健康检查
This commit is contained in:
@@ -1,123 +1,123 @@
|
||||
import axios from 'axios'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import router from '@/router'
|
||||
|
||||
const api = axios.create({
|
||||
baseURL: '',
|
||||
timeout: 30000
|
||||
})
|
||||
|
||||
/**
|
||||
* 解析 API 错误响应
|
||||
*/
|
||||
function parseApiError(error) {
|
||||
const result = {
|
||||
code: 'UNKNOWN_ERROR',
|
||||
message: '发生了未知错误',
|
||||
traceId: '',
|
||||
status: 500
|
||||
}
|
||||
|
||||
// 网络错误(后端未启动、网络断开等)
|
||||
if (!error.response) {
|
||||
if (error.code === 'ECONNABORTED') {
|
||||
result.code = 'TIMEOUT_ERROR'
|
||||
result.message = '请求超时,请稍后重试'
|
||||
result.status = 0
|
||||
} else if (error.message?.includes('Network Error')) {
|
||||
result.code = 'SERVICE_UNAVAILABLE'
|
||||
result.message = '服务暂时不可用,请稍后重试'
|
||||
result.status = 503
|
||||
} else {
|
||||
result.code = 'NETWORK_ERROR'
|
||||
result.message = '网络连接失败,请检查网络后重试'
|
||||
result.status = 0
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
const { status, data, headers } = error.response
|
||||
result.status = status
|
||||
result.traceId = headers['x-trace-id'] || headers['X-Trace-ID'] || ''
|
||||
|
||||
if (data && data.error) {
|
||||
result.code = data.error.code || result.code
|
||||
result.message = data.error.message || result.message
|
||||
result.traceId = data.error.trace_id || result.traceId
|
||||
} else if (data && data.detail) {
|
||||
result.message = typeof data.detail === 'string' ? data.detail : JSON.stringify(data.detail)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* 跳转到错误页面(使用 sessionStorage + replace,不影响浏览器历史)
|
||||
*/
|
||||
function navigateToErrorPage(errorInfo) {
|
||||
// 记录当前页面路径(用于返回)
|
||||
sessionStorage.setItem('errorFromPath', router.currentRoute.value.fullPath)
|
||||
|
||||
// 保存错误信息到 sessionStorage(不会显示在 URL 中)
|
||||
sessionStorage.setItem('errorInfo', JSON.stringify({
|
||||
code: errorInfo.code,
|
||||
message: errorInfo.message,
|
||||
traceId: errorInfo.traceId,
|
||||
status: errorInfo.status,
|
||||
timestamp: Date.now()
|
||||
}))
|
||||
|
||||
// 使用 replace 而不是 push,这样浏览器返回时不会停留在错误页
|
||||
router.replace({ name: 'Error' })
|
||||
}
|
||||
|
||||
// 请求拦截器
|
||||
api.interceptors.request.use(
|
||||
config => {
|
||||
const token = localStorage.getItem('token')
|
||||
if (token) {
|
||||
config.headers.Authorization = `Bearer ${token}`
|
||||
}
|
||||
return config
|
||||
},
|
||||
error => Promise.reject(error)
|
||||
)
|
||||
|
||||
// 响应拦截器(集成 TraceID 追踪)
|
||||
api.interceptors.response.use(
|
||||
response => response,
|
||||
error => {
|
||||
const errorInfo = parseApiError(error)
|
||||
const traceLog = errorInfo.traceId ? ` (trace: ${errorInfo.traceId})` : ''
|
||||
|
||||
console.error(`[API Error] ${errorInfo.code}: ${errorInfo.message}${traceLog}`)
|
||||
|
||||
// 严重错误列表(跳转错误页)
|
||||
const criticalErrors = [
|
||||
'INTERNAL_ERROR',
|
||||
'SERVICE_UNAVAILABLE',
|
||||
'GATEWAY_ERROR',
|
||||
'NETWORK_ERROR',
|
||||
'TIMEOUT_ERROR'
|
||||
]
|
||||
|
||||
if (error.response?.status === 401) {
|
||||
localStorage.removeItem('token')
|
||||
localStorage.removeItem('user')
|
||||
router.push('/login')
|
||||
ElMessage.error('登录已过期,请重新登录')
|
||||
} else if (error.response?.status === 403) {
|
||||
ElMessage.error('没有权限执行此操作')
|
||||
} else if (criticalErrors.includes(errorInfo.code)) {
|
||||
// 严重错误(包括网络错误、服务不可用)跳转到错误页面
|
||||
navigateToErrorPage(errorInfo)
|
||||
} else {
|
||||
// 普通错误显示消息
|
||||
ElMessage.error(errorInfo.message)
|
||||
}
|
||||
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
export default api
|
||||
import axios from 'axios'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import router from '@/router'
|
||||
|
||||
const api = axios.create({
|
||||
baseURL: '',
|
||||
timeout: 30000
|
||||
})
|
||||
|
||||
/**
|
||||
* 解析 API 错误响应
|
||||
*/
|
||||
function parseApiError(error) {
|
||||
const result = {
|
||||
code: 'UNKNOWN_ERROR',
|
||||
message: '发生了未知错误',
|
||||
traceId: '',
|
||||
status: 500
|
||||
}
|
||||
|
||||
// 网络错误(后端未启动、网络断开等)
|
||||
if (!error.response) {
|
||||
if (error.code === 'ECONNABORTED') {
|
||||
result.code = 'TIMEOUT_ERROR'
|
||||
result.message = '请求超时,请稍后重试'
|
||||
result.status = 0
|
||||
} else if (error.message?.includes('Network Error')) {
|
||||
result.code = 'SERVICE_UNAVAILABLE'
|
||||
result.message = '服务暂时不可用,请稍后重试'
|
||||
result.status = 503
|
||||
} else {
|
||||
result.code = 'NETWORK_ERROR'
|
||||
result.message = '网络连接失败,请检查网络后重试'
|
||||
result.status = 0
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
const { status, data, headers } = error.response
|
||||
result.status = status
|
||||
result.traceId = headers['x-trace-id'] || headers['X-Trace-ID'] || ''
|
||||
|
||||
if (data && data.error) {
|
||||
result.code = data.error.code || result.code
|
||||
result.message = data.error.message || result.message
|
||||
result.traceId = data.error.trace_id || result.traceId
|
||||
} else if (data && data.detail) {
|
||||
result.message = typeof data.detail === 'string' ? data.detail : JSON.stringify(data.detail)
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* 跳转到错误页面(使用 sessionStorage + replace,不影响浏览器历史)
|
||||
*/
|
||||
function navigateToErrorPage(errorInfo) {
|
||||
// 记录当前页面路径(用于返回)
|
||||
sessionStorage.setItem('errorFromPath', router.currentRoute.value.fullPath)
|
||||
|
||||
// 保存错误信息到 sessionStorage(不会显示在 URL 中)
|
||||
sessionStorage.setItem('errorInfo', JSON.stringify({
|
||||
code: errorInfo.code,
|
||||
message: errorInfo.message,
|
||||
traceId: errorInfo.traceId,
|
||||
status: errorInfo.status,
|
||||
timestamp: Date.now()
|
||||
}))
|
||||
|
||||
// 使用 replace 而不是 push,这样浏览器返回时不会停留在错误页
|
||||
router.replace({ name: 'Error' })
|
||||
}
|
||||
|
||||
// 请求拦截器
|
||||
api.interceptors.request.use(
|
||||
config => {
|
||||
const token = localStorage.getItem('token')
|
||||
if (token) {
|
||||
config.headers.Authorization = `Bearer ${token}`
|
||||
}
|
||||
return config
|
||||
},
|
||||
error => Promise.reject(error)
|
||||
)
|
||||
|
||||
// 响应拦截器(集成 TraceID 追踪)
|
||||
api.interceptors.response.use(
|
||||
response => response,
|
||||
error => {
|
||||
const errorInfo = parseApiError(error)
|
||||
const traceLog = errorInfo.traceId ? ` (trace: ${errorInfo.traceId})` : ''
|
||||
|
||||
console.error(`[API Error] ${errorInfo.code}: ${errorInfo.message}${traceLog}`)
|
||||
|
||||
// 严重错误列表(跳转错误页)
|
||||
const criticalErrors = [
|
||||
'INTERNAL_ERROR',
|
||||
'SERVICE_UNAVAILABLE',
|
||||
'GATEWAY_ERROR',
|
||||
'NETWORK_ERROR',
|
||||
'TIMEOUT_ERROR'
|
||||
]
|
||||
|
||||
if (error.response?.status === 401) {
|
||||
localStorage.removeItem('token')
|
||||
localStorage.removeItem('user')
|
||||
router.push('/login')
|
||||
ElMessage.error('登录已过期,请重新登录')
|
||||
} else if (error.response?.status === 403) {
|
||||
ElMessage.error('没有权限执行此操作')
|
||||
} else if (criticalErrors.includes(errorInfo.code)) {
|
||||
// 严重错误(包括网络错误、服务不可用)跳转到错误页面
|
||||
navigateToErrorPage(errorInfo)
|
||||
} else {
|
||||
// 普通错误显示消息
|
||||
ElMessage.error(errorInfo.message)
|
||||
}
|
||||
|
||||
return Promise.reject(error)
|
||||
}
|
||||
)
|
||||
|
||||
export default api
|
||||
|
||||
Reference in New Issue
Block a user