feat(plan): 计划自动化执行、结果列表与鉴权请求优化

- 新增 automationApi、PlanAutomationRun、PlanAutomationExecutionList
- 计划构建/列表/执行/进度等接入自动化与路由
- request 与 authToken 处理 token 刷新与错误码

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
qiaoxinjiu
2026-05-11 14:27:55 +08:00
parent 33753361b0
commit 6e9673f7dd
14 changed files with 1587 additions and 48 deletions

58
src/utils/authToken.js Normal file
View File

@@ -0,0 +1,58 @@
import axios from 'axios'
/** 与 request 实例一致,避免走带拦截器的 axios 造成循环 */
const REFRESH_URL = '/it/api/auth/refresh'
let inflightRefresh = null
/**
* 静默续期POST /auth/refresh仅当业务接口返回 code 451 时由 request 响应拦截器调用)
* body 优先 refreshToken否则传 accessToken成功 code=20000 且 data.token
* @returns {Promise<boolean>}
*/
export function tryRefreshAccessToken() {
if (inflightRefresh) {
return inflightRefresh
}
const refreshToken = localStorage.getItem('refreshToken')
const accessToken = localStorage.getItem('accessToken')
if (!refreshToken && !accessToken) {
return Promise.resolve(false)
}
inflightRefresh = axios({
method: 'post',
url: REFRESH_URL,
timeout: 30000,
headers: {
'Content-Type': 'application/json',
...(accessToken ? { accessToken } : {})
},
data: refreshToken ? { refreshToken } : accessToken ? { accessToken } : {}
})
.then(res => {
const body = res && res.data
if (!body || body.code !== 20000) {
return false
}
const d = body.data || {}
const token = d.token || body.token
if (token) {
localStorage.setItem('accessToken', token)
}
const rt = d.refresh_token || d.refreshToken
if (rt) {
localStorage.setItem('refreshToken', rt)
}
return !!token
})
.catch(() => false)
.finally(() => {
inflightRefresh = null
})
return inflightRefresh
}
export function clearTokenStorage() {
localStorage.removeItem('accessToken')
localStorage.removeItem('refreshToken')
}

View File

@@ -1,15 +1,65 @@
import axios from 'axios'
import { Message } from 'element-ui';
import { Message } from 'element-ui'
import router from '../router/index'
const service = axios.create({
// baseURL: 'http://10.250.0.252:5010', // api 的 base_url
// baseURL: '', // api 的 base_url
baseURL: '/it/api', // api 的 base_url
import store from '@/vuex/store'
import { clearTokenStorage, tryRefreshAccessToken } from './authToken'
timeout: 90000 // request timeout
const service = axios.create({
baseURL: '/it/api',
timeout: 90000
})
// 请求拦截 设置统一header
function pushLoginExpired(message) {
clearTokenStorage()
localStorage.removeItem('authUser')
localStorage.removeItem('userMenus')
store.commit('ClearCurrentUser')
router.push({ name: 'login' })
Message.error(message || '登录已失效,请重新登录')
}
function apiCode(data) {
if (!data || data.code === undefined || data.code === null) return null
const n = Number(data.code)
return Number.isFinite(n) ? n : null
}
function isMissingToken(data) {
return apiCode(data) === 40001
}
/** 仅 451token 无效或已过期,走静默续期并重试一次 */
function isTokenExpiredRefreshable(data) {
return apiCode(data) === 451
}
function isForbiddenApi(data) {
return apiCode(data) === 40003
}
/** @param {{ config: object, data?: object }} ctx */
function handleTokenExpiredRefreshAndRetry(ctx) {
const cfg = (ctx && ctx.config) || {}
const pdata = (ctx && ctx.data) || {}
if (cfg.__retriedTokenRefresh) {
pushLoginExpired(pdata.message || 'token无效或已过期')
return Promise.reject(new Error('token无效或已过期'))
}
return tryRefreshAccessToken().then(ok => {
if (!ok) {
pushLoginExpired(pdata.message || 'token无效或已过期')
return Promise.reject(new Error('token无效或已过期'))
}
const nextCfg = Object.assign({}, cfg, { __retriedTokenRefresh: true })
nextCfg.headers = Object.assign({}, nextCfg.headers || {})
const newAt = localStorage.getItem('accessToken')
if (newAt) {
nextCfg.headers.accessToken = newAt
}
return service.request(nextCfg)
})
}
service.interceptors.request.use(
config => {
const accessToken = localStorage.getItem('accessToken')
@@ -18,36 +68,51 @@ service.interceptors.request.use(
}
return config
},
error => {
return Promise.reject(error)
}
error => Promise.reject(error)
)
// 响应拦截 401 token过期处理
service.interceptors.response.use(
response => {
const data = response && response.data ? response.data : {}
// 兼容后端返回结构:{ success, code, message, data }
if (data && data.code === 500) {
Message.error('服务异常')
return Promise.reject(new Error(data.message || '服务异常'))
} else if (data && data.code === 451) {
router.push({ name: 'login' })
Message.error(data.message || '登录已失效,请重新登录')
return Promise.reject(new Error(data.message || '登录已失效'))
} else if (data && data.success === false) {
Message.error(data.message || '请求失败')
return Promise.reject(new Error(data.message || '请求失败'))
} else if (data && data.code !== undefined && data.code !== 20000) {
Message.error(data.message || '请求失败')
return Promise.reject(new Error(data.message || '请求失败'))
} else {
return response.data
}
if (data && isMissingToken(data)) {
pushLoginExpired(data.message || '缺少token')
return Promise.reject(new Error(data.message || '缺少token'))
}
if (data && isTokenExpiredRefreshable(data)) {
return handleTokenExpiredRefreshAndRetry({ config: response.config, data })
}
if (data && isForbiddenApi(data)) {
Message.error(data.message || '无权限访问该接口!')
return Promise.reject(new Error(data.message || '无权限访问该接口'))
}
if (data && data.success === false) {
Message.error(data.message || '请求失败')
return Promise.reject(new Error(data.message || '请求失败'))
}
if (data && data.code !== undefined && data.code !== 20000) {
Message.error(data.message || '请求失败')
return Promise.reject(new Error(data.message || '请求失败'))
}
return response.data
},
error => {
// 非 2xx 时会进入这里(如 40009/40012后端通常会带 JSON body
const data = error && error.response && error.response.data ? error.response.data : null
const res = error && error.response
const data = res && res.data ? res.data : null
if (data && isMissingToken(data) && error.config) {
pushLoginExpired(data.message || '缺少token')
return Promise.reject(new Error(data.message || '缺少token'))
}
if (data && isTokenExpiredRefreshable(data) && error.config && !error.config.__retriedTokenRefresh) {
return handleTokenExpiredRefreshAndRetry({ config: error.config, data })
}
if (data && isForbiddenApi(data)) {
Message.error(data.message || '无权限访问该接口!')
return Promise.reject(error)
}
if (data && typeof data === 'object') {
if (data.success === false) {
Message.error(data.message || '请求失败')