项目安全问题——前端两步完成加密
前言我之前有写过项目安全问题-SM4加解密_react前端sm4加密参数-CSDN博客这个加密当然需要后端配合此次加密是为了应对银行的安全测试,保证项目不被从接口请求进行攻击值得借鉴加密细节描述一、登录接口请求body进行sm4加密后传输加密key65d3ea57d0756fcc4e553e04d96006e3原body内容{loginName:rrms,passWord:xmbankUser123}加密后body内容9Tz/F/02LdIq1y4xNXD/Sn9u/FsdgGwS5iXDrYWnVNCC7jMe4qzAriXFlMmhKtR1wYEjM/8q28M6HbjY0RKIQ请求示例二、所有接口调用前统一增加签名。涉及到的参数有1、header新增参数nonceUUID随机数、timestamp13位时间戳、signature生成的签名2、生成签名signature的规则将header中的参数以及请求接口最后的路径拼接成下面格式后noncexxxresourcexxx×tampxxxtokenxxx签名signature字符串类型,根据请求类型传不同的参数GET请求http://101.200.125.197:9000/cbrc/users/userList?pageNo1pageSize20nameorgIddepIdposIdroleId加密参数有值的请求参数nonce、timestamp、resource、token、pageNo、pageSize最终拼接的加密串noncef40ab8e1113d4074a6fa0bbf7f9616d3pageNo1pageSize20resource/userListtimestamp1755505087425tokeneyJhbGc...POST请求非登陆请求和上传文件请求http://101.200.125.197:9000/cbrc/users/updateUser加密参数有值的请求参数nonce、timestamp、resource、token、body最终拼接的加密串body{id:1226602,name:cybfgld,workNo:cybfgld,orgId:1216,depId:12574,positionId:9,email:liujinxinwiseweb.com.cn,roleId:3,loginName:cybfgld,userStatus:0,telephone:,vacationDatesArray:[],vacationDates:,transUser:null,approvalModuleArray:[1,2,3],approvalModule:1,2,3,sex:0,positionName:/,createTime:2025-06-10 16:48:23,createUserId:1226593,updateByUser:0,userFrom:0,organizationVo:{id:1216,name:總行,parentId:1215,parentName:null,type:2,orgType:1,createTime:2025-04-27 15:20:58,updateTime:2025-04-27 15:20:58},departmentVo:{id:12574,orgId:1216,orgName:总行,name:AO(总行办公室),type:1,updateTime:2025-05-21 15:47:07,createTime:2025-05-21 15:47:03,userId:null,userName:null,userNumber:null},roleVo:{id:3,name:声誉组,isReputation:1}}noncef40ab8e1113d4074a6fa0bbf7f9616d3resource/updateUsertimestamp1755505340216tokeneyJhbGciOiJIUzU...3、使用HMAC-SHA256生成签名签名的key随意定jianzhenbank2025示例我调用接口地址 http://101.200.125.197:9000/cbrc/workOrg/reputation/queryPage?orgType1pageNo1pageSize20orgId1116stagekeyType1flag其中resource取最后一个/到之前部分即resource/queryPagenonceb307b7c84ce84f078748619ad0c7a57ctimestamp1752822334952tokeneyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJpZCUzRDElMjZuYW1lJTNEJUU3JUFFJUExJUU3JTkwJTg2JUU1JTkxJTk4JTI2cmlkJTNEMSUyNnJuYW1lJTNEJUU4JUI2JTg1JUU3JUJBJUE3JUU3JUFFJUExJUU3JTkwJTg2JUU1JTkxJTk4JTI2b2lkJTNEMTExNiUyNm9uYW1lJTNEJUU2JTgwJUJCJUU4JUExJThDJTI2b3JnVHlwZSUzRDElMjZvcmdQYXJlbnRJZCUzRDExMTUlMjZkZXBJZCUzRDEyMzQzJTI2ZGVwTmFtZSUzRCVFOCVBMSU4QyVFOSVBMiU4NiVFNSVBRiVCQyUyNmRlcFR5cGUlM0QyJTI2cG9zSWQlM0QxJTI2cG9zTmFtZSUzRCVFNyVCQiU4RiVFNSU4QSU5RSUyNndvcmtObyUzRG51bGwlMjZlbWFpbCUzRGxpdWppbnhpbiU0MHdpc2V3ZWIuY29tLmNuJTI2b3JnRGF0YVNvdXJjZSUzRFhJQU1FTiIsImlhdCI6MTc1MjgyMjEwNSwiZXhwIjoxNzUyODI5MzA1fQ.VrZNOlbf4c5aRCa45S386gTOinYwmyttqNxzYlU67a95FfXMnhYkO054FJ5Sbjf0R9H7i13SnECBvt4Wa5Rz3g最终生成的签名signature6c48a43d13680a9a0b049d9b65541879553f943b5be981c96574a8113bb198c4开始加密第一步安装依赖安装npm i sm-crypto随机数,安装 npm i uuid安装npm install --save crypto-js!DOCTYPE html html langzh-CN head meta charsetUTF-8 meta nameviewport contentwidthdevice-width, initial-scale1.0 titleVue2 UUID生成方法/title script srchttps://cdn.jsdelivr.net/npm/vue2.6.14/dist/vue.js/script script srchttps://cdn.jsdelivr.net/npm/uuid8.3.2/dist/uuidv4.min.js/script style * { box-sizing: border-box; margin: 0; padding: 0; } body { font-family: Segoe UI, Tahoma, Geneva, Verdana, sans-serif; line-height: 1.6; color: #333; background-color: #f5f7fa; padding: 20px; } .container { max-width: 1000px; margin: 0 auto; padding: 20px; } header { text-align: center; margin-bottom: 30px; padding: 20px; background: linear-gradient(135deg, #6a11cb 0%, #2575fc 100%); color: white; border-radius: 10px; box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1); } h1 { margin-bottom: 10px; } .description { max-width: 800px; margin: 0 auto 30px; padding: 20px; background-color: white; border-radius: 10px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05); } .methods { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); gap: 20px; margin-bottom: 30px; } .method { background-color: white; padding: 20px; border-radius: 10px; box-shadow: 0 2px 10px rgba(0, 0, 0, 0.05); transition: transform 0.3s ease; } .method:hover { transform: translateY(-5px); box-shadow: 0 5px 15px rgba(0, 0, 0, 0.1); } .method h3 { color: #2575fc; margin-bottom: 15px; padding-bottom: 10px; border-bottom: 1px solid #eee; } .uuid-display { margin: 15px 0; padding: 12px; background-color: #f8f9fa; border-radius: 5px; font-family: monospace; word-break: break-all; border: 1px solid #e9ecef; } .btn { display: inline-block; padding: 10px 20px; background-color: #2575fc; color: white; border: none; border-radius: 5px; cursor: pointer; font-size: 16px; transition: background-color 0.3s; } .btn:hover { background-color: #1c64e0; } .btn-copy { background-color: #4caf50; margin-left: 10px; } .btn-copy:hover { background-color: #43a047; } .notes { margin-top: 15px; padding: 10px; background-color: #fff9e6; border-left: 4px solid #ffc107; font-size: 14px; } .footer { text-align: center; margin-top: 40px; padding: 20px; color: #6c757d; } media (max-width: 768px) { .methods { grid-template-columns: 1fr; } } /style /head body div idapp classcontainer header h1Vue2 UUID生成方法/h1 p多种方式生成全局唯一标识符/p /header div classdescription pUUID通用唯一识别码是用于信息在系统中唯一标识的128位数值。在Vue2项目中有多种方法可以生成UUID各有优缺点适用于不同场景。/p /div div classmethods div classmethod h31. 使用uuid库/h3 p最常用的方法通过npm安装uuid库/p div classuuid-display{{ uuidV4 }}/div button classbtn clickgenerateV4生成UUID/button button classbtn btn-copy clickcopyToClipboard(uuidV4)复制/button div classnotes strong注意/strong需要先安装uuid库codenpm install uuid/code /div /div div classmethod h32. 使用Crypto API/h3 p使用浏览器内置的Crypto API生成UUID/p div classuuid-display{{ cryptoUUID }}/div button classbtn clickgenerateCrypto生成UUID/button button classbtn btn-copy clickcopyToClipboard(cryptoUUID)复制/button div classnotes strong注意/strong需要现代浏览器支持不支持IE浏览器 /div /div div classmethod h33. 自定义算法/h3 p使用自定义算法生成符合UUID格式的字符串/p div classuuid-display{{ customUUID }}/div button classbtn clickgenerateCustom生成UUID/button button classbtn btn-copy clickcopyToClipboard(customUUID)复制/button div classnotes strong注意/strong这不是标准实现但适用于大多数需要唯一标识符的场景 /div /div div classmethod h34. 时间戳简易ID/h3 p基于时间戳生成的简易唯一ID/p div classuuid-display{{ simpleId }}/div button classbtn clickgenerateSimple生成ID/button button classbtn btn-copy clickcopyToClipboard(simpleId)复制/button div classnotes strong注意/strong这不是UUID但在简单场景下可以作为唯一标识符使用 /div /div /div div classfooter p© 2023 Vue2 UUID生成示例 | 选择适合您项目需求的UUID生成方法/p /div /div script new Vue({ el: #app, data: { uuidV4: , cryptoUUID: , customUUID: , simpleId: }, mounted() { this.generateV4(); this.generateCrypto(); this.generateCustom(); this.generateSimple(); }, methods: { // 方法1: 使用uuid库 generateV4() { this.uuidV4 uuidv4(); }, // 方法2: 使用Crypto API现代浏览器 generateCrypto() { if (crypto crypto.randomUUID) { this.cryptoUUID crypto.randomUUID(); } else { this.cryptoUUID 您的浏览器不支持Crypto.randomUUID(); } }, // 方法3: 自定义UUID生成算法 generateCustom() { function generateUUID() { return xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx.replace(/[xy]/g, function(c) { const r Math.random() * 16 | 0; const v c x ? r : (r 0x3 | 0x8); return v.toString(16); }); } this.customUUID generateUUID(); }, // 方法4: 基于时间戳的简易ID generateSimple() { this.simpleId id_ Date.now() _ Math.random().toString(36).substr(2, 9); }, // 复制到剪贴板 copyToClipboard(text) { if (text.includes(不支持)) return; const textArea document.createElement(textarea); textArea.value text; document.body.appendChild(textArea); textArea.select(); try { document.execCommand(copy); alert(已复制到剪贴板: text); } catch (err) { alert(复制失败: err); } document.body.removeChild(textArea); } } }); /script /body /html第二步写独立的加密方法文件signature.jsimport CryptoJS from crypto-js; // import { v4 as uuidv4 } from uuid; // 签名密钥 const SIGN_KEY jianzhenbank2025; /** * 从URL中提取resource * param {string} url 接口地址 * returns {string} 提取的resource */ export function extractResource(url) { if (typeof url ! string) { return ; } // 移除URL中的查询参数 const pureUrl url.split(?)[0]; // 找到最后一个/的位置 const lastSlashIndex pureUrl.lastIndexOf(/); return pureUrl.substring(lastSlashIndex); } /** * 生成签名 * param {Object} params 签名参数 * returns {string} 签名结果 */ // export function generateSignature(params) { // // 按照指定格式拼接参数 // const paramString nonce${params.nonce}resource${params.resource}timestamp${params.timestamp}token${params.token || }; // // 使用HMAC-SHA256生成签名 // return CryptoJS.HmacSHA256(paramString, SIGN_KEY).toString(CryptoJS.enc.Hex); // } export function generateSignature(params) { // 按照指定格式拼接参数 // 使用HMAC-SHA256生成签名 console.log(加密前,params); return CryptoJS.HmacSHA256(params, SIGN_KEY).toString(CryptoJS.enc.Hex); } /** * 生成13位时间戳 * returns {number} 时间戳 */ export function getTimestamp() { return Date.now(); } /** * 生成UUID * returns {string} UUID */ // export function generateNonce() { // return uuidv4().replace(/-/g, ); // } // 方法4: 基于时间戳的简易ID export function generateNonce() { return id_ Date.now() _ Math.random().toString(36).substr(2, 9); } /** * 生成签名字符串 * param {Object} params - 签名参数对象 * param {String} method - 请求方法 * param {Object} config - axios请求配置 * returns {String} 拼接后的签名字符串 */ export function generateSignatureString(params, method, config) { let signatureParts {}; // 通用参数 signatureParts.nonce params.nonce; signatureParts.resource params.resource; signatureParts.timestamp params.timestamp; signatureParts.token params.token; // console.log(config.params,config.params); // GET请求拼接所有有值的查询参数 if (method get) { const urlParams new URLSearchParams(config.params || {}); for (const [key, value] of urlParams.entries()) { if (value ! undefined value ! undefined value ! null value ! null value ! ) { signatureParts[key] value; } } } // POST请求非登录/非文件上传拼接body else if (method post !isLoginOrUpload(config)) { if (config.data typeof config.data object) { signatureParts.body JSON.stringify(config.data); } } // 其他请求类型可按需扩展 // // 剔除值为 null 或 null 的参数 // Object.keys(signatureParts).forEach(key { // if (signatureParts[key] null || signatureParts[key] null) { // delete signatureParts[key]; // } // }); // TreeMap式排序 const sortedParams Object.keys(signatureParts) .sort() .reduce((acc, key) { acc[key] signatureParts[key]; return acc; }, {}); // 按顺序拼接使用分隔 const signatureStr Object.entries(sortedParams) .map(([key, value]) ${key}${value}) .join(); return generateSignature(signatureStr); } /** * 判断是否为登录或文件上传请求 * param {Object} config - axios请求配置 * returns {Boolean} */ export function isLoginOrUpload(config) { const loginUrls [/login]; const uploadUrls [/upload, /file/upload]; const url config.url || ; return loginUrls.some(u url.endsWith(u)) || uploadUrls.some(u url.endsWith(u)) || (config.headers config.headers[Content-Type] config.headers[Content-Type].includes(multipart/form-data)); }sm4.js// utils/sm4.js import { sm4 } from sm-crypto; // SM4 加密密钥16字节128位 const secretKey 65d3ea57d0756fcc4e553e04d96006e3; // 加密函数 export function encryptData(data) { // 将对象转换为 JSON 字符串 const dataString typeof data string ? data : JSON.stringify(data); // 使用 SM4-ECB 模式加密不使用向量 const encryptedHex sm4.encrypt(dataString, secretKey); // 将加密后的十六进制字符串转换为 Base64 编码 const encryptedBase64 btoa(hexToUint8Array(encryptedHex).reduce((data, byte) { return data String.fromCharCode(byte); }, )); return encryptedBase64; } // 辅助函数将十六进制字符串转换为 Uint8Array function hexToUint8Array(hex) { const arrayBuffer new Uint8Array(hex.length / 2); for (let i 0; i hex.length; i 2) { const byteValue parseInt(hex.substr(i, 2), 16); if (isNaN(byteValue)) { throw new Error(Invalid hex string); } arrayBuffer[i / 2] byteValue; } return arrayBuffer; }第三步写请求拦截器逻辑请求拦截器(http.interceptors.request)所在文件import { encryptData } from /js/sm4.js; import { extractResource, generateSignature, getTimestamp, generateNonce,generateSignatureString,isLoginOrUpload } from /js/signature.js; const API_WHITELIST [ /login, // 登录接口 ]; window._axiosPromiseArr [] // axios中设置放置要取消的对象 Vue.prototype.$http.interceptors.request.use((config) { const isWhitelisted API_WHITELIST.some(url config.url.endsWith(url) ); // 只对登录接口进行加密 if (isWhitelisted) { // 加密请求数据 if (config.data) { config.data encryptData(config.data) } } const token localStorage.getItem(header) if (token) { config.headers.Authorization token } // 1. 生成基础参数 const nonce generateNonce(); const timestamp getTimestamp(); // 2. 提取resource从完整URL中 const fullUrl config.baseURL ? config.baseURL config.url : config.url; const urlWithoutParams fullUrl.includes(?) ? fullUrl.split(?)[0] : fullUrl; const resource extractResource(urlWithoutParams); // 3. 生成签名字符串根据请求类型和参数 const signatureStr generateSignatureString({ nonce, timestamp, resource, token }, config.method.toLowerCase(), config); console.log(注意了,上面打印的是这接口加密前urlWithoutParams接口加密后,signatureStr); // 4. 添加到请求头 config.headers { ...config.headers, nonce, timestamp, signature: signatureStr, }; return config })注意事项:如果登录请求接口/login加密后无法正常传参请指定Content-Type: application/json // 显式指定/*接口请求*/ this.$http({ method: post, url: /cbrc/login, headers: { code: this.code, imgkey: this.imgkey, Content-Type: application/json // 显式指定 }, data: data, })最终效果请求标头:POST /cbrc/docinfo/updateDocinfStatus HTTP/1.1 Accept: application/json, text/plain, */* Accept-Encoding: gzip, deflate, br, zstd Accept-Language: zh-CN,zh;q0.9 Authorization: eyJhbGciOiJIUzUxMiIsInppcCI6IkdaSVAifQ.H4sIAAAAAAAAAB2MSw7CMBBD7zJrImXayXfLBbjCJCGQSkRIaaVSxN0Z2Nl-tt-wrA0ihMyFZquVz9kqcqGqlEkrwzU5ClPRWOEEjVeI6IzxFDzOEowhay6P1gWPLYmbEAOZeNy535YD4-UsiLfyK_aX6Ov-_L9Yh7O8fL7oa3gzhQAAAA.96WjgyzLREkys_hqoj4M7nvmx7rLrmtFcfEUmAhftu4HTL6yvc8nn5JQ6EeDXpT1m0gAMEjOgoxPc82upGhTVw Connection: keep-alive Content-Length: 412 Content-Type: application/json;charsetUTF-8 Cookie: parent_qimo_sid_1129a340-f365-11eb-98e3-dfa9d16ffc4af73bd7db-c51b-4013-bc9f-76ce03e86256; hrefhttp%3A%2F%2Flocalhost%3A8989%2F; accessId1129a340-f365-11eb-98e3-dfa9d16ffc4a; qimo_seosource_0%E7%AB%99%E5%86%85; qimo_seokeywords_0; qimo_seosource_1129a340-f365-11eb-98e3-dfa9d16ffc4a%E7%AB%99%E5%86%85; qimo_seokeywords_1129a340-f365-11eb-98e3-dfa9d16ffc4a; qimo_xstKeywords_1129a340-f365-11eb-98e3-dfa9d16ffc4a; headereyJhbGciOiJIUzUxMiIsInppcCI6IkdaSVAifQ.H4sIAAAAAAAAAB2MSw7CMBBD7zJrImXayXfLBbjCJCGQSkRIaaVSxN0Z2Nl-tt-wrA0ihMyFZquVz9kqcqGqlEkrwzU5ClPRWOEEjVeI6IzxFDzOEowhay6P1gWPLYmbEAOZeNy535YD4-UsiLfyK_aX6Ov-_L9Yh7O8fL7oa3gzhQAAAA.96WjgyzLREkys_hqoj4M7nvmx7rLrmtFcfEUmAhftu4HTL6yvc8nn5JQ6EeDXpT1m0gAMEjOgoxPc82upGhTVw; pageViewNum6 Host: localhost:8989 Origin: http://localhost:8989 Sec-Fetch-Dest: empty Sec-Fetch-Mode: cors Sec-Fetch-Site: same-origin User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36 nonce: id_1755849870531_yrq9wrby5 sec-ch-ua: Not)A;Brand;v8, Chromium;v138, Google Chrome;v138 sec-ch-ua-mobile: ?0 sec-ch-ua-platform: Windows signature: f0d943fdd90a7c385bf8e854d7042026952fbfce1a38d76b0c3d314e8bb6a3e2 timestamp: 1755849870531后续出现的问题与解决方案问题:当前js文件的加密方法不太完美, 如代码A( this.$http({ method: get, url: /cbrc/dropDown/data_dic?typeId1pid2 }) ),代码B( this.$http({ method: get, url: /cbrc/dropDown/data_dic?typeIdpid, params:{ typeId:1, pid:2 } }) ),当我在加密拼接参数的时候,如果是代码A就无法正常获取参数值并拼接,如果是代码B这种传参方式就能正常传参,我想无论哪种传参方式都能正常获取到并拼接,修改signature.js文件/*优化说明1. 新增 parseUrlQueryParams 方法专门解析URL中的查询参数为对象保证无论参数在URL还是params字段都能被正确获取和拼接。2. 在 generateSignatureString 方法中GET请求时会合并URL和config.params中的参数优先以config.params为准覆盖URL中的同名参数保证两种传参方式都能正常加密。3. 其他逻辑保持原有注释和结构增强代码可读性和健壮性。*/import CryptoJS from crypto-js; // import { v4 as uuidv4 } from uuid; // 签名密钥 const SIGN_KEY xiamenbank2025; /** * 从URL中提取resource * param {string} url 接口地址 * returns {string} 提取的resource */ export function extractResource(url) { if (typeof url ! string) { return ; } // 移除URL中的查询参数 const pureUrl url.split(?)[0]; // 找到最后一个/的位置 const lastSlashIndex pureUrl.lastIndexOf(/); return pureUrl.substring(lastSlashIndex); } /** * 生成签名 * param {Object} params 签名参数 * returns {string} 签名结果 */ // export function generateSignature(params) { // // 按照指定格式拼接参数 // const paramString nonce${params.nonce}resource${params.resource}timestamp${params.timestamp}token${params.token || }; // // 使用HMAC-SHA256生成签名 // return CryptoJS.HmacSHA256(paramString, SIGN_KEY).toString(CryptoJS.enc.Hex); // } export function generateSignature(params) { // 按照指定格式拼接参数 // 使用HMAC-SHA256生成签名 console.log(加密前, params); return CryptoJS.HmacSHA256(params, SIGN_KEY).toString(CryptoJS.enc.Hex); } /** * 生成13位时间戳 * returns {number} 时间戳 */ export function getTimestamp() { return Date.now(); } /** * 生成UUID * returns {string} UUID */ // export function generateNonce() { // return uuidv4().replace(/-/g, ); // } // 方法4: 基于时间戳的简易ID export function generateNonce() { return id_ Date.now() _ Math.random().toString(36).substr(2, 9); } /** * 解析URL中的查询参数为对象 * param {string} url - 完整URL字符串 * returns {Object} 查询参数对象 */ function parseUrlQueryParams(url) { const paramsObj {}; if (typeof url ! string) return paramsObj; const queryStr url.split(?)[1]; if (!queryStr) return paramsObj; const urlParams new URLSearchParams(queryStr); for (const [key, value] of urlParams.entries()) { // 只添加有key的参数 paramsObj[key] value; } return paramsObj; } /** * 生成签名字符串 * param {Object} params - 签名参数对象 * param {String} method - 请求方法 * param {Object} config - axios请求配置 * returns {String} 拼接后的签名字符串 */ export function generateSignatureString(params, method, config) { let signatureParts {}; // 通用参数 signatureParts.nonce params.nonce; signatureParts.resource params.resource; signatureParts.timestamp params.timestamp; signatureParts.token params.token; // 统一处理GET请求参数无论参数在URL还是params字段 if (method get) { // 1. 解析URL中的查询参数 const urlQueryParams parseUrlQueryParams(config.url || ); // 2. 解析config.params中的参数 const configParams config.params || {}; // 合并两者优先以config.params为准覆盖URL中的同名参数 const mergedParams { ...urlQueryParams, ...configParams }; // 只拼接有值的参数 Object.keys(mergedParams).forEach(key { const value mergedParams[key]; if ( value ! undefined value ! undefined value ! null value ! null value ! ) { signatureParts[key] value; } }); } // POST请求非登录/非文件上传拼接body else if (method post !isLoginOrUpload(config)) { if (config.data typeof config.data object) { signatureParts.body JSON.stringify(config.data); } } // 其他请求类型可按需扩展 // // 剔除值为 null 或 null 的参数 // Object.keys(signatureParts).forEach(key { // if (signatureParts[key] null || signatureParts[key] null) { // delete signatureParts[key]; // } // }); // TreeMap式排序 const sortedParams Object.keys(signatureParts) .sort() .reduce((acc, key) { acc[key] signatureParts[key]; return acc; }, {}); // 按顺序拼接使用分隔 const signatureStr Object.entries(sortedParams) .map(([key, value]) ${key}${value}) .join(); return generateSignature(signatureStr); } /** * 判断是否为登录或文件上传请求 * param {Object} config - axios请求配置 * returns {Boolean} */ export function isLoginOrUpload(config) { const loginUrls [/login]; const uploadUrls [/upload, /file/upload]; const url config.url || ; return loginUrls.some(u url.endsWith(u)) || uploadUrls.some(u url.endsWith(u)) || (config.headers config.headers[Content-Type] config.headers[Content-Type].includes(multipart/form-data)); } /* 优化说明 1. 新增 parseUrlQueryParams 方法专门解析URL中的查询参数为对象保证无论参数在URL还是params字段都能被正确获取和拼接。 2. 在 generateSignatureString 方法中GET请求时会合并URL和config.params中的参数优先以config.params为准覆盖URL中的同名参数保证两种传参方式都能正常加密。 3. 其他逻辑保持原有注释和结构增强代码可读性和健壮性。 */