@ -1,51 +1,270 @@
import $cache from '@/plugins/cache' ;
import useUserStore from '@/store/modules/user' ;
import request from '@/utils/request' ;
import axios from 'axios' ;
import sign from '@/utils/sign.js' ;
import CryptoJS from 'crypto-js' ;
/**
* 包装 request 请求,统一使用 Go 服务地址作为 baseURL
* @param config 原始请求配置
*/
const BASE_GO_URL = import . meta . env . VITE_APP_BASE_API_GO ;
const token = $cache . local . get ( 'goToken' ) ;
interface RequestGo extends Function {
( config : any ) : Promise < any > ;
download ? : ( url : string , params : any , filename : string ) = > void ;
}
const requestGo : RequestGo = ( config : any ) = > {
return request ( {
baseURL : BASE_GO_URL ,
. . . config ,
headers : {
'Authorization' : ` Bearer ${ $cache . local . get ( 'goToken' ) || '' } `
// 加密密钥
const corySecretKey = 'happyCoryOrTieHanHan202410151415' ;
// 配置新建一个 axios 实例
const service = axios . create ( {
baseURL : BASE_GO_URL as any ,
timeout : 120000 ,
headers : { 'Content-Type' : 'application/json' }
} ) ;
// 不需要修改项目id的接口数组
const whiteUrl = [
'/api/wxApplet/wxApplet/sysProjectTeam/list' ,
'/api/wxApplet/wxApplet/sysProjectTeam/add' ,
'/api/wxApplet/wxApplet/sysProjectTeam/edit' ,
'/api/v1/test/testFollowInfo/add' ,
'/api/wxApplet/wxApplet/busConstructionUser/changePay' ,
// 获取项目资料文件夹
'/api/v1/system/documentData/treeStructureData' ,
'/api/v1/system/busConstructionUser/exportSalary' ,
'/api/v1/system/busSalaryDetails/list' ,
'/api/v1/system/ys7Devices/add' ,
'/api/v1/system/ys7Devices/edit' ,
'/api/v1/system/subProject/add' ,
'/api/v1/system/subProject/edit' ,
'/api/v1/system/subProject/list' ,
'/api/v1/system/workStatus/getTree' ,
'/api/v1/system/sysProjectIntroduce/list' ,
'/api/v1/system/sysProjectIntroduce/add' ,
'/api/v1/system/sysProjectIntroduce/edit' ,
'/video/hat/manage/api/v1/video/device/list' ,
'/video/hat/manage/api/v1/video/project/bind' ,
'/api/v1/system/notifications/publish' ,
'/api/v1/system/notifications/list' ,
'/api/v1/system/notifications/edit' ,
'/webodm/api/v1/taskCreate' ,
'/webodm/api/v1/taskProcess' ,
'/webodm/api/v1/download' ,
'/api/v1/system/manageTaskRecord/upDataResource' ,
'/api/v1/system/ys7Devices/list' ,
'/api/v1/system/busFolderFile/add' ,
'/api/v1/system/manageTaskRecordResource/voluntarilyReq' ,
'/api/v1/system/busAttendanceMachine/edit' ,
'/api/v1/system/qianqiFangzhen/add' ,
'/api/v1/system/qianqiNibianqi/add'
] ;
const exceptionStr = '/api/v1/test/' ; // /api/v1/test/*接口拦截
// 添加请求拦截器
service . interceptors . request . use (
( config : any ) = > {
// 在发送请求之前做些什么 token
if ( token ) {
config . headers = config . headers || { } ;
( config . headers as any ) [ 'Authorization' ] = ` Bearer ${ token } ` ;
}
} ) ;
} ;
const stores = useUserStore ( ) ;
if ( ! whiteUrl . includes ( config . url ) && ! config . url . includes ( exceptionStr ) ) {
if ( config . params && Reflect . has ( config . params , 'projectId' ) ) {
config . params . projectId = stores . selectedProject . goId ;
}
// 处理FormData中的projectId
if ( config . data instanceof FormData && config . data . has ( 'projectId' ) ) {
config . data . set ( 'projectId' , stores . selectedProject . goId ) ;
}
}
let dataInfoReq = { } ;
if ( config . method === 'get' || config . method === 'delete' ) {
config . params = config . params || { } ;
dataInfoReq = JSON . parse ( JSON . stringify ( config . params ) ) ;
const encryptedParam = encryptAES256 ( JSON . stringify ( dataInfoReq ) , corySecretKey ) ;
if ( $cache . local . get ( 'i18n' ) != null ) {
config . params = { coryKey : encryptedParam , corySimplifiedToTraditional : $cache.local.get ( 'i18n' ) } ;
} else {
config . params = { coryKey : encryptedParam } ;
}
} else {
// 处理FormData类型
if ( config . data instanceof FormData ) {
// 将FormData转换为普通对象
dataInfoReq = Object . fromEntries ( config . data . entries ( ) ) ;
// 重新创建一个新的FormData实例
const newFormData = new FormData ( ) ;
const encryptedData = encryptAES256 ( JSON . stringify ( dataInfoReq ) , corySecretKey ) ;
requestGo . download = function ( url : string , params : any , filename : string , method : 'post' | 'get' = 'post' ) {
return request ( {
url ,
method : method ,
baseURL : BASE_GO_URL ,
data : method === 'post' ? params : undefined ,
params : method === 'get' ? params : undefined ,
headers : {
'Authorization' : ` Bearer ${ $cache . local . get ( 'goToken' ) || '' } `
newFormData . append ( 'coryKey' , encryptedData ) ;
if ( $cache . local . get ( 'i18n' ) != null ) {
newFormData . append ( 'corySimplifiedToTraditional' , $cache . local . get ( 'i18n' ) ) ;
}
// 如果有文件,需要重新添加
for ( let [ key , value ] of config . data . entries ( ) ) {
if ( value instanceof File ) {
newFormData . append ( key , value ) ;
}
}
config . data = newFormData ;
// 设置Content-Type为undefined, 让浏览器自动设置boundary
config . headers [ 'Content-Type' ] = undefined ;
} else {
dataInfoReq = config . data ;
const encryptedData = encryptAES256 ( JSON . stringify ( dataInfoReq ) , corySecretKey ) ;
if ( $cache . local . get ( 'i18n' ) != null ) {
config . data = { coryKey : encryptedData , corySimplifiedToTraditional : $cache.local.get ( 'i18n' ) } ;
} else {
config . data = { coryKey : encryptedData } ;
}
}
}
const { timestamp , nonce , sign : signature } = sign ( dataInfoReq ) ;
config . headers . timestamp = timestamp ;
config . headers . nonce = nonce ;
config . headers . sign = signature ;
return config ;
} ,
responseType : 'blob'
} ) . then ( ( response ) = > {
// ✅ 只取 response.data
const blob = new Blob ( [ response . data ] ) ;
const link = document . createElement ( 'a' ) ;
link . style . display = 'none' ;
link . href = URL . c reateObjectURL ( blob ) ;
link . setAttribute ( 'download' , filename ) ;
document . body . appendChild ( link ) ;
link . click ( ) ;
document . body . removeChild ( link ) ;
URL . revokeObjectURL ( link . href ) ;
} ) ;
};
( error ) = > {
// 对请求错误做些什么
return Promise . reject ( error ) ;
}
) ;
// 添加响应拦截器
service . interceptors . response . use (
( response ) = > {
// 对响应数据进行解密操作
try {
// 处理特殊情况: data为空值或不需要解密的情况
if ( response . data . data === null || response . data . data === '' || response . data . data === undefined ) {
return response . data ;
}
export default requestGo ;
// 检查响应数据格式
if ( typeof response . data . data !== 'string' ) {
// 尝试将非字符串数据转换为字符串
try {
response . data . data = JSON . stringify ( response . data . data ) ;
} catch ( convertError ) {
throw new Error ( '响应数据格式不正确,无法转换为字符串' ) ;
}
}
// 执行解密
const decryptedData = decryptAES256 ( response . data . data , corySecretKey ) ;
// 正确处理解密后的数据
if ( decryptedData && typeof decryptedData === 'object' ) {
// 保持response.data的结构不变, 只替换data字段的内容
response . data = {
. . . response . data ,
data : decryptedData
} ;
} else {
// 如果解密后的数据不是对象,可能需要特殊处理
response . data . data = decryptedData ;
}
if ( response . data . code !== 0 ) {
ElMessage . error ( response . data . message ) ;
}
return response . data ;
} catch ( decryptError ) {
// 提供更友好的错误信息
if ( decryptError . message . includes ( '无效的Base64格式' ) ) {
ElMessage . error ( '数据格式错误:接收到无效的加密数据' ) ;
} else {
ElMessage . error ( '数据解密失败,请联系管理员' ) ;
}
return Promise . reject ( decryptError ) ;
}
} ,
( error ) = > {
// 对响应错误做点什么
if ( error . message . indexOf ( 'timeout' ) !== - 1 ) {
ElMessage . error ( '网络超时' ) ;
} else if ( error . message === 'Network Error' ) {
ElMessage . error ( '网络连接错误' ) ;
} else {
console . log ( error , '网络错误' ) ;
if ( error . response . data ) ElMessage . error ( error . response . statusText ) ;
else {
ElMessage . error ( '接口路径找不到' ) ;
console . log ( error , '接口路径找不到' ) ;
}
}
return Promise . reject ( error ) ;
}
) ;
// 加密函数
function encryptAES256 ( plainText : string , key : string ) : string | null {
try {
const parsedKey = CryptoJS . enc . Utf8 . parse ( key ) ;
const iv = CryptoJS . lib . WordArray . random ( 16 ) ;
const plainTextWordArray = CryptoJS . enc . Utf8 . parse ( plainText ) ;
const encrypted = CryptoJS . AES . encrypt ( plainTextWordArray , parsedKey , {
iv : iv ,
mode : CryptoJS.mode.CBC ,
padding : CryptoJS.pad.Pkcs7
} ) ;
const encryptedData = iv . concat ( encrypted . ciphertext ) ;
return encryptedData . toString ( CryptoJS . enc . Base64 ) ;
} catch ( err ) {
console . error ( 'Encryption error:' , err . message ) ;
return null ;
}
}
// 增强的解密函数,处理各种输入情况
function decryptAES256 ( encryptedData : string , key : string ) : any {
// eslint-disable-next-line no-useless-catch
try {
// 检查输入是否为有效的Base64字符串
if ( ! /^[A-Za-z0-9+/=]+$/ . test ( encryptedData ) ) {
// 尝试对非标准Base64字符串进行处理
// 移除可能的前缀
const cleanData = encryptedData . replace ( /^data:.*?;base64,/ , '' ) . replace ( /\s/g , '' ) ; // 移除空格
if ( ! /^[A-Za-z0-9+/=]+$/ . test ( cleanData ) ) {
throw new Error ( '无效的Base64格式' ) ;
}
return decryptAES256 ( cleanData , key ) ;
}
const decodedData = CryptoJS . enc . Base64 . parse ( encryptedData ) ;
const iv = CryptoJS . lib . WordArray . create ( decodedData . words . slice ( 0 , 4 ) ) ;
const encryptedText = CryptoJS . lib . WordArray . create ( decodedData . words . slice ( 4 ) ) ;
const parsedKey = CryptoJS . enc . Utf8 . parse ( key ) ;
const decrypted = CryptoJS . AES . decrypt ( { ciphertext : encryptedText } , parsedKey , {
iv : iv ,
mode : CryptoJS.mode.CBC ,
padding : CryptoJS.pad.Pkcs7
} ) ;
const decryptedStr = decrypted . toString ( CryptoJS . enc . Utf8 ) ;
// 尝试解析为JSON
try {
return JSON . parse ( decryptedStr ) ;
} catch ( jsonError ) {
return decryptedStr ;
}
} catch ( err ) {
throw err ;
}
}
// 导出 axios 实例
export default service ;