董国庆
2026-02-10 c9a1e955f7c83b6484f9c0f601ea0b1de98dd5dd
src/service/http/alova.ts
@@ -14,6 +14,29 @@
  handleResponseError,
  handleServiceResult,
} from './handle'
import {
  aesEncrypt,
  generateAesKey,
  generateHeaders,
  generateSign,
  generateSignKey,
  getTimestamp,
  rsaEncrypt,
} from '@/utils/crypto'
import { v4 as uuidv4 } from 'uuid'
export interface EncryptedRequestOptions<T = any> {
  sensitiveFields?: string[]
  rsaPublicKey?: string
  secretKey?: string
  meta?: any
}
export interface EncryptedRequestConfig {
  path: string
  data?: any
  options?: EncryptedRequestOptions
}
const { onAuthRequired, onResponseRefreshToken } = createServerTokenAuthentication<VueHookType>({
  // 服务端判定token过期
@@ -63,6 +86,94 @@
        method.config.headers['Content-Type'] = 'application/x-www-form-urlencoded'
        method.data = new URLSearchParams(method.data as URLSearchParams).toString()
      }
      // 加密和签名逻辑
      const timestamp = getTimestamp()
      const rsaPublicKey = method.meta?.rsaPublicKey || '' // 从meta中获取RSA公钥
      const secretKey = method.meta?.secretKey || '' // 从meta中获取签名密钥
      const sensitiveFields = method.meta?.sensitiveFields || [] // 从meta中获取敏感字段
      console.log('secretKey', secretKey)
      console.log('method.meta', method.meta, 'method.data', method.data)
      if (secretKey) {
        if (method.data && typeof method.data === 'object') {
          // 分离敏感字段和非敏感字段
          const sensitiveData: Record<string, any> = {}
          const normalData: Record<string, any> = {}
          Object.keys(method.data).forEach((key) => {
            if (sensitiveFields.includes(key)) {
              sensitiveData[key] = method.data[key]
            }
            else {
              normalData[key] = method.data[key]
            }
          })
          // 加密敏感数据
          if (Object.keys(sensitiveData).length > 0 && rsaPublicKey) {
            const aesKey = generateAesKey()
            const encryptedAesKey = rsaEncrypt(aesKey, rsaPublicKey)
            // 将所有敏感字段一起加密成一个密文
            const encryptedData = aesEncrypt(JSON.stringify(sensitiveData), aesKey)
            // 替换敏感字段为加密后的数据(保持字段名不变)
            const encryptedParams: Record<string, any> = {}
            Object.keys(sensitiveData).forEach((key) => {
              encryptedParams[key] = encryptedData
            })
            method.data = {
              ...normalData,
              ...encryptedParams,
            }
            // 生成签名,使用加密后的数据
            const signParams = {
              ...normalData,
              ...encryptedParams,
            }
            const sign = generateSign(signParams, secretKey, timestamp)
            // 添加请求头
            const headers = generateHeaders(sign, encryptedAesKey, timestamp)
            Object.assign(method.config.headers, headers)
          }
          else {
            // 所有接口都需要签名,即使没有敏感数据
            const sign = generateSign(method.data, secretKey, timestamp)
            // 添加签名相关请求头
            const headers = {
              timestamp: timestamp.toString(),
              nonce: secretKey,
              sign,
            }
            Object.assign(method.config.headers, headers)
          }
        }
        else {
          // 没有传参数,但仍然需要添加时间戳和签名
          // 对于无参数的请求,默认传一个时间戳的参数
          const paramsWithTimestamp = {
            timestamp: timestamp.toString(),
          }
          // 使用包含时间戳的对象生成签名
          const sign = generateSign(paramsWithTimestamp, secretKey, timestamp)
          console.log('method', method)
          // 将时间戳作为参数传递
          method.data = paramsWithTimestamp
          // 添加签名相关请求头
          const headers = {
            timestamp: timestamp.toString(),
            nonce: secretKey,
            sign,
          }
          Object.assign(method.config.headers, headers)
        }
      }
      alovaConfig.beforeRequest?.(method)
    }),
    responded: onResponseRefreshToken({
@@ -100,3 +211,39 @@
    }),
  })
}
export async function createEncryptedRequest<T = any>(
  requestInstance: any,
  config: EncryptedRequestConfig,
): Promise<Service.ResponseResult<T>> {
  const { path, data, options = {} } = config
  const { sensitiveFields = [], rsaPublicKey: providedPublicKey, secretKey: providedSecretKey, meta = {} } = options
  let rsaPublicKey = providedPublicKey
  let secretKey = providedSecretKey
  if (sensitiveFields.length > 0 && !rsaPublicKey) {
    secretKey = generateSignKey()
    const rsaResult = await requestInstance.Post<Service.ResponseResult<string>>('/account/base/v1/getRsaPubKey', {}).send()
    if (!rsaResult.isSuccess || !rsaResult.data) {
      throw new Error('获取RSA公钥失败')
    }
    rsaPublicKey = rsaResult.data
  }
  if (!secretKey) {
    secretKey = generateSignKey()
  }
  const methodInstance = requestInstance.Post<Service.ResponseResult<T>>(path, data || {})
  methodInstance.meta = {
    ...meta,
    rsaPublicKey,
    secretKey,
    sensitiveFields,
  }
  return methodInstance.send()
}