- 同步版本号:VERSION / backend pom / admin-web / miniapp 均升至 1.1.0 - 合并本轮代码审查修复(详见 docs/CODE-REVIEW-2026-09-24.md): - 后端:微信登录真实实现 RealWechatLoginProvider、实名核验真实实现 RealIdentityVerificationProvider(本地强校验 + 可配置第三方 + fail-closed/fail-fast) - 照片直传落库端点 POST /me/photos/confirm(fileKey 归属校验 + 对象存在性校验后 savePhoto) - 刷新 Token Redis TTL 修正(getExpirationMs()*2 的 4h → refreshExpirationMs 的 14 天) - 管理端 token 改为 HttpOnly Cookie + silent-refresh,移除 localStorage 明文存储与 30+ 处手工注入 - 小程序:红娘接单鉴权头错位修复、serviceType 中文映射、生产域名可配置 - 隔离本地产物(.workbuddy/、*.bundle、*.dump、secrets、application-dev.yml 等已被 .gitignore 覆盖) 未验证项(打标签 ≠ 生产发布): - 后端编译 / 单测未在本环境执行(Maven 慢、Docker DNS 受限) - 管理端 登录→刷新→登出 端到端、小程序真机走查待人工验收 - 生产部署需注入 WECHAT_APP_ID / WECHAT_APP_SECRET、VERIFICATION_API_URL / VERIFICATION_API_KEY 与小程序生产域名
85 lines
2.7 KiB
TypeScript
85 lines
2.7 KiB
TypeScript
import axios from 'axios'
|
|
import { ElMessage } from 'element-plus'
|
|
import router from '@/router'
|
|
import { getAccessToken, setAccessToken, clearAccessToken } from '@/utils/token'
|
|
|
|
const client = axios.create({
|
|
baseURL: '/api/v1',
|
|
timeout: 10000,
|
|
})
|
|
|
|
// 防止 401 时并发触发多次刷新
|
|
let refreshing = false
|
|
|
|
client.interceptors.request.use((config) => {
|
|
const token = getAccessToken()
|
|
if (token) {
|
|
config.headers.Authorization = `Bearer ${token}`
|
|
}
|
|
return config
|
|
})
|
|
|
|
client.interceptors.response.use(
|
|
(res) => res.data,
|
|
async (err) => {
|
|
const status = err.response?.status
|
|
const original = err.config
|
|
const isAuthEndpoint = !!original?.url?.includes('/auth/operator/')
|
|
|
|
// access token 过期:尝试用 HttpOnly refresh Cookie 静默刷新一次后重试原请求
|
|
// __retried 防止刷新后重试仍 401 时再次触发刷新,形成循环
|
|
if (status === 401 && !refreshing && !isAuthEndpoint && !(original as any)?.__retried) {
|
|
refreshing = true
|
|
try {
|
|
const resp: any = await client.post('/auth/operator/refresh', {})
|
|
if (resp?.success) {
|
|
const newToken = resp.data.accessToken
|
|
setAccessToken(newToken)
|
|
refreshing = false
|
|
;(original as any).__retried = true
|
|
const headers = (original.headers || {}) as Record<string, string>
|
|
headers.Authorization = `Bearer ${newToken}`
|
|
original.headers = headers as any
|
|
return client(original)
|
|
}
|
|
} catch {
|
|
// 刷新失败:清除并跳登录
|
|
} finally {
|
|
refreshing = false
|
|
}
|
|
clearAccessToken()
|
|
ElMessage.error('登录已过期,请重新登录')
|
|
router.push('/login')
|
|
return Promise.reject(err)
|
|
}
|
|
|
|
// 刷新/登录等鉴权端点自身 401:交由调用方处理文案,不全局弹窗跳转
|
|
if (status === 401) {
|
|
if (!isAuthEndpoint) {
|
|
clearAccessToken()
|
|
ElMessage.error('登录已过期,请重新登录')
|
|
router.push('/login')
|
|
}
|
|
} else if (err.response?.data?.message) {
|
|
ElMessage.error(err.response.data.message)
|
|
} else {
|
|
ElMessage.error('请求失败')
|
|
}
|
|
return Promise.reject(err)
|
|
}
|
|
)
|
|
|
|
export const get = (url: string, params?: Record<string, any>, headers?: Record<string, string>) =>
|
|
client.get(url, { params, headers })
|
|
|
|
export const post = (url: string, data?: any, headers?: Record<string, string>) =>
|
|
client.post(url, data, { headers })
|
|
|
|
export const put = (url: string, data?: any, headers?: Record<string, string>) =>
|
|
client.put(url, data, { headers })
|
|
|
|
export const del = (url: string, headers?: Record<string, string>) =>
|
|
client.delete(url, { headers })
|
|
|
|
export default client
|