feat:【framework 框架】增加 api 加解密能力(优化代码)
parent
2920dabb99
commit
4be6d26375
|
@ -47,10 +47,8 @@
|
||||||
"@vueuse/integrations": "catalog:",
|
"@vueuse/integrations": "catalog:",
|
||||||
"ant-design-vue": "catalog:",
|
"ant-design-vue": "catalog:",
|
||||||
"cropperjs": "catalog:",
|
"cropperjs": "catalog:",
|
||||||
"crypto-js": "catalog:",
|
|
||||||
"dayjs": "catalog:",
|
"dayjs": "catalog:",
|
||||||
"highlight.js": "catalog:",
|
"highlight.js": "catalog:",
|
||||||
"jsencrypt": "catalog:",
|
|
||||||
"pinia": "catalog:",
|
"pinia": "catalog:",
|
||||||
"vue": "catalog:",
|
"vue": "catalog:",
|
||||||
"vue-dompurify-html": "catalog:",
|
"vue-dompurify-html": "catalog:",
|
||||||
|
|
|
@ -12,16 +12,17 @@ import {
|
||||||
RequestClient,
|
RequestClient,
|
||||||
} from '@vben/request';
|
} from '@vben/request';
|
||||||
import { useAccessStore } from '@vben/stores';
|
import { useAccessStore } from '@vben/stores';
|
||||||
|
import { createApiEncrypt } from '@vben/utils';
|
||||||
|
|
||||||
import { message } from 'ant-design-vue';
|
import { message } from 'ant-design-vue';
|
||||||
|
|
||||||
import { useAuthStore } from '#/store';
|
import { useAuthStore } from '#/store';
|
||||||
import { ApiEncrypt } from '#/utils/encrypt';
|
|
||||||
|
|
||||||
import { refreshTokenApi } from './core';
|
import { refreshTokenApi } from './core';
|
||||||
|
|
||||||
const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
|
const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
|
||||||
const tenantEnable = isTenantEnable();
|
const tenantEnable = isTenantEnable();
|
||||||
|
const apiEncrypt = createApiEncrypt(import.meta.env);
|
||||||
|
|
||||||
function createRequestClient(baseURL: string, options?: RequestClientOptions) {
|
function createRequestClient(baseURL: string, options?: RequestClientOptions) {
|
||||||
const client = new RequestClient({
|
const client = new RequestClient({
|
||||||
|
@ -91,9 +92,9 @@ function createRequestClient(baseURL: string, options?: RequestClientOptions) {
|
||||||
try {
|
try {
|
||||||
// 加密请求数据
|
// 加密请求数据
|
||||||
if (config.data) {
|
if (config.data) {
|
||||||
config.data = ApiEncrypt.encryptRequest(config.data);
|
config.data = apiEncrypt.encryptRequest(config.data);
|
||||||
// 设置加密标识头
|
// 设置加密标识头
|
||||||
config.headers[ApiEncrypt.getEncryptHeader()] = 'true';
|
config.headers[apiEncrypt.getEncryptHeader()] = 'true';
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('请求数据加密失败:', error);
|
console.error('请求数据加密失败:', error);
|
||||||
|
@ -108,14 +109,14 @@ function createRequestClient(baseURL: string, options?: RequestClientOptions) {
|
||||||
client.addResponseInterceptor({
|
client.addResponseInterceptor({
|
||||||
fulfilled: (response) => {
|
fulfilled: (response) => {
|
||||||
// 检查是否需要解密响应数据
|
// 检查是否需要解密响应数据
|
||||||
const encryptHeader = ApiEncrypt.getEncryptHeader();
|
const encryptHeader = apiEncrypt.getEncryptHeader();
|
||||||
const isEncryptResponse =
|
const isEncryptResponse =
|
||||||
response.headers[encryptHeader] === 'true' ||
|
response.headers[encryptHeader] === 'true' ||
|
||||||
response.headers[encryptHeader.toLowerCase()] === 'true';
|
response.headers[encryptHeader.toLowerCase()] === 'true';
|
||||||
if (isEncryptResponse && typeof response.data === 'string') {
|
if (isEncryptResponse && typeof response.data === 'string') {
|
||||||
try {
|
try {
|
||||||
// 解密响应数据
|
// 解密响应数据
|
||||||
response.data = ApiEncrypt.decryptResponse(response.data);
|
response.data = apiEncrypt.decryptResponse(response.data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('响应数据解密失败:', error);
|
console.error('响应数据解密失败:', error);
|
||||||
throw new Error(`响应数据解密失败: ${(error as Error).message}`);
|
throw new Error(`响应数据解密失败: ${(error as Error).message}`);
|
||||||
|
|
|
@ -45,11 +45,9 @@
|
||||||
"@vben/utils": "workspace:*",
|
"@vben/utils": "workspace:*",
|
||||||
"@vueuse/core": "catalog:",
|
"@vueuse/core": "catalog:",
|
||||||
"cropperjs": "catalog:",
|
"cropperjs": "catalog:",
|
||||||
"crypto-js": "catalog:",
|
|
||||||
"dayjs": "catalog:",
|
"dayjs": "catalog:",
|
||||||
"element-plus": "catalog:",
|
"element-plus": "catalog:",
|
||||||
"highlight.js": "catalog:",
|
"highlight.js": "catalog:",
|
||||||
"jsencrypt": "catalog:",
|
|
||||||
"pinia": "catalog:",
|
"pinia": "catalog:",
|
||||||
"vue": "catalog:",
|
"vue": "catalog:",
|
||||||
"vue-router": "catalog:"
|
"vue-router": "catalog:"
|
||||||
|
|
|
@ -12,16 +12,17 @@ import {
|
||||||
RequestClient,
|
RequestClient,
|
||||||
} from '@vben/request';
|
} from '@vben/request';
|
||||||
import { useAccessStore } from '@vben/stores';
|
import { useAccessStore } from '@vben/stores';
|
||||||
|
import { createApiEncrypt } from '@vben/utils';
|
||||||
|
|
||||||
import { ElMessage } from 'element-plus';
|
import { ElMessage } from 'element-plus';
|
||||||
|
|
||||||
import { useAuthStore } from '#/store';
|
import { useAuthStore } from '#/store';
|
||||||
import { ApiEncrypt } from '#/utils/encrypt';
|
|
||||||
|
|
||||||
import { refreshTokenApi } from './core';
|
import { refreshTokenApi } from './core';
|
||||||
|
|
||||||
const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
|
const { apiURL } = useAppConfig(import.meta.env, import.meta.env.PROD);
|
||||||
const tenantEnable = isTenantEnable();
|
const tenantEnable = isTenantEnable();
|
||||||
|
const apiEncrypt = createApiEncrypt(import.meta.env);
|
||||||
|
|
||||||
function createRequestClient(baseURL: string, options?: RequestClientOptions) {
|
function createRequestClient(baseURL: string, options?: RequestClientOptions) {
|
||||||
const client = new RequestClient({
|
const client = new RequestClient({
|
||||||
|
@ -91,9 +92,9 @@ function createRequestClient(baseURL: string, options?: RequestClientOptions) {
|
||||||
try {
|
try {
|
||||||
// 加密请求数据
|
// 加密请求数据
|
||||||
if (config.data) {
|
if (config.data) {
|
||||||
config.data = ApiEncrypt.encryptRequest(config.data);
|
config.data = apiEncrypt.encryptRequest(config.data);
|
||||||
// 设置加密标识头
|
// 设置加密标识头
|
||||||
config.headers[ApiEncrypt.getEncryptHeader()] = 'true';
|
config.headers[apiEncrypt.getEncryptHeader()] = 'true';
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('请求数据加密失败:', error);
|
console.error('请求数据加密失败:', error);
|
||||||
|
@ -108,14 +109,14 @@ function createRequestClient(baseURL: string, options?: RequestClientOptions) {
|
||||||
client.addResponseInterceptor({
|
client.addResponseInterceptor({
|
||||||
fulfilled: (response) => {
|
fulfilled: (response) => {
|
||||||
// 检查是否需要解密响应数据
|
// 检查是否需要解密响应数据
|
||||||
const encryptHeader = ApiEncrypt.getEncryptHeader();
|
const encryptHeader = apiEncrypt.getEncryptHeader();
|
||||||
const isEncryptResponse =
|
const isEncryptResponse =
|
||||||
response.headers[encryptHeader] === 'true' ||
|
response.headers[encryptHeader] === 'true' ||
|
||||||
response.headers[encryptHeader.toLowerCase()] === 'true';
|
response.headers[encryptHeader.toLowerCase()] === 'true';
|
||||||
if (isEncryptResponse && typeof response.data === 'string') {
|
if (isEncryptResponse && typeof response.data === 'string') {
|
||||||
try {
|
try {
|
||||||
// 解密响应数据
|
// 解密响应数据
|
||||||
response.data = ApiEncrypt.decryptResponse(response.data);
|
response.data = apiEncrypt.decryptResponse(response.data);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('响应数据解密失败:', error);
|
console.error('响应数据解密失败:', error);
|
||||||
throw new Error(`响应数据解密失败: ${(error as Error).message}`);
|
throw new Error(`响应数据解密失败: ${(error as Error).message}`);
|
||||||
|
|
|
@ -1,240 +0,0 @@
|
||||||
import CryptoJS from 'crypto-js';
|
|
||||||
import { JSEncrypt } from 'jsencrypt';
|
|
||||||
|
|
||||||
/**
|
|
||||||
* API 加解密工具类
|
|
||||||
* 支持 AES 和 RSA 加密算法
|
|
||||||
*/
|
|
||||||
|
|
||||||
// 从环境变量获取配置
|
|
||||||
const API_ENCRYPT_ENABLE =
|
|
||||||
import.meta.env.VITE_APP_API_ENCRYPT_ENABLE === 'true';
|
|
||||||
const API_ENCRYPT_HEADER =
|
|
||||||
import.meta.env.VITE_APP_API_ENCRYPT_HEADER || 'X-Api-Encrypt';
|
|
||||||
const API_ENCRYPT_ALGORITHM =
|
|
||||||
import.meta.env.VITE_APP_API_ENCRYPT_ALGORITHM || 'AES';
|
|
||||||
const API_ENCRYPT_REQUEST_KEY =
|
|
||||||
import.meta.env.VITE_APP_API_ENCRYPT_REQUEST_KEY || ''; // AES密钥 或 RSA公钥
|
|
||||||
const API_ENCRYPT_RESPONSE_KEY =
|
|
||||||
import.meta.env.VITE_APP_API_ENCRYPT_RESPONSE_KEY || ''; // AES密钥 或 RSA私钥
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AES 加密工具类
|
|
||||||
*/
|
|
||||||
export const AES = {
|
|
||||||
/**
|
|
||||||
* AES 加密
|
|
||||||
* @param data 要加密的数据
|
|
||||||
* @param key 加密密钥
|
|
||||||
* @returns 加密后的字符串
|
|
||||||
*/
|
|
||||||
encrypt(data: string, key: string): string {
|
|
||||||
try {
|
|
||||||
if (!key) {
|
|
||||||
throw new Error('AES 加密密钥不能为空');
|
|
||||||
}
|
|
||||||
if (key.length !== 32) {
|
|
||||||
throw new Error(
|
|
||||||
`AES 加密密钥长度必须为 32 位,当前长度: ${key.length}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const keyUtf8 = CryptoJS.enc.Utf8.parse(key);
|
|
||||||
const encrypted = CryptoJS.AES.encrypt(data, keyUtf8, {
|
|
||||||
mode: CryptoJS.mode.ECB,
|
|
||||||
padding: CryptoJS.pad.Pkcs7,
|
|
||||||
});
|
|
||||||
return encrypted.toString();
|
|
||||||
} catch (error) {
|
|
||||||
console.error('AES 加密失败:', error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AES 解密
|
|
||||||
* @param encryptedData 加密的数据
|
|
||||||
* @param key 解密密钥
|
|
||||||
* @returns 解密后的字符串
|
|
||||||
*/
|
|
||||||
decrypt(encryptedData: string, key: string): string {
|
|
||||||
try {
|
|
||||||
if (!key) {
|
|
||||||
throw new Error('AES 解密密钥不能为空');
|
|
||||||
}
|
|
||||||
if (key.length !== 32) {
|
|
||||||
throw new Error(
|
|
||||||
`AES 解密密钥长度必须为 32 位,当前长度: ${key.length}`,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (!encryptedData) {
|
|
||||||
throw new Error('AES 解密数据不能为空');
|
|
||||||
}
|
|
||||||
|
|
||||||
const keyUtf8 = CryptoJS.enc.Utf8.parse(key);
|
|
||||||
const decrypted = CryptoJS.AES.decrypt(encryptedData, keyUtf8, {
|
|
||||||
mode: CryptoJS.mode.ECB,
|
|
||||||
padding: CryptoJS.pad.Pkcs7,
|
|
||||||
});
|
|
||||||
const result = decrypted.toString(CryptoJS.enc.Utf8);
|
|
||||||
if (!result) {
|
|
||||||
throw new Error('AES 解密结果为空,可能是密钥错误或数据损坏');
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('AES 解密失败:', error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* RSA 加密工具类
|
|
||||||
*/
|
|
||||||
export const RSA = {
|
|
||||||
/**
|
|
||||||
* RSA 加密
|
|
||||||
* @param data 要加密的数据
|
|
||||||
* @param publicKey 公钥(必需)
|
|
||||||
* @returns 加密后的字符串
|
|
||||||
*/
|
|
||||||
encrypt(data: string, publicKey: string): false | string {
|
|
||||||
try {
|
|
||||||
if (!publicKey) {
|
|
||||||
throw new Error('RSA 公钥不能为空');
|
|
||||||
}
|
|
||||||
|
|
||||||
const encryptor = new JSEncrypt();
|
|
||||||
encryptor.setPublicKey(publicKey);
|
|
||||||
const result = encryptor.encrypt(data);
|
|
||||||
if (result === false) {
|
|
||||||
throw new Error('RSA 加密失败,可能是公钥格式错误或数据过长');
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('RSA 加密失败:', error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* RSA 解密
|
|
||||||
* @param encryptedData 加密的数据
|
|
||||||
* @param privateKey 私钥(必需)
|
|
||||||
* @returns 解密后的字符串
|
|
||||||
*/
|
|
||||||
decrypt(encryptedData: string, privateKey: string): false | string {
|
|
||||||
try {
|
|
||||||
if (!privateKey) {
|
|
||||||
throw new Error('RSA 私钥不能为空');
|
|
||||||
}
|
|
||||||
if (!encryptedData) {
|
|
||||||
throw new Error('RSA 解密数据不能为空');
|
|
||||||
}
|
|
||||||
|
|
||||||
const encryptor = new JSEncrypt();
|
|
||||||
encryptor.setPrivateKey(privateKey);
|
|
||||||
const result = encryptor.decrypt(encryptedData);
|
|
||||||
if (result === false) {
|
|
||||||
throw new Error('RSA 解密失败,可能是私钥错误或数据损坏');
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
} catch (error) {
|
|
||||||
console.error('RSA 解密失败:', error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
|
||||||
* API 加解密主类
|
|
||||||
*/
|
|
||||||
export const ApiEncrypt = {
|
|
||||||
/**
|
|
||||||
* 获取加密头名称
|
|
||||||
*/
|
|
||||||
getEncryptHeader(): string {
|
|
||||||
return API_ENCRYPT_HEADER;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 加密请求数据
|
|
||||||
* @param data 要加密的数据
|
|
||||||
* @returns 加密后的数据
|
|
||||||
*/
|
|
||||||
encryptRequest(data: any): string {
|
|
||||||
if (!API_ENCRYPT_ENABLE) {
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const jsonData = typeof data === 'string' ? data : JSON.stringify(data);
|
|
||||||
|
|
||||||
if (API_ENCRYPT_ALGORITHM.toUpperCase() === 'AES') {
|
|
||||||
if (!API_ENCRYPT_REQUEST_KEY) {
|
|
||||||
throw new Error('AES 请求加密密钥未配置');
|
|
||||||
}
|
|
||||||
return AES.encrypt(jsonData, API_ENCRYPT_REQUEST_KEY);
|
|
||||||
} else if (API_ENCRYPT_ALGORITHM.toUpperCase() === 'RSA') {
|
|
||||||
if (!API_ENCRYPT_REQUEST_KEY) {
|
|
||||||
throw new Error('RSA 公钥未配置');
|
|
||||||
}
|
|
||||||
const result = RSA.encrypt(jsonData, API_ENCRYPT_REQUEST_KEY);
|
|
||||||
if (result === false) {
|
|
||||||
throw new Error('RSA 加密失败');
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
} else {
|
|
||||||
throw new Error(`不支持的加密算法: ${API_ENCRYPT_ALGORITHM}`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('请求数据加密失败:', error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 解密响应数据
|
|
||||||
* @param encryptedData 加密的响应数据
|
|
||||||
* @returns 解密后的数据
|
|
||||||
*/
|
|
||||||
decryptResponse(encryptedData: string): any {
|
|
||||||
if (!API_ENCRYPT_ENABLE) {
|
|
||||||
return encryptedData;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
let decryptedData: false | string = '';
|
|
||||||
if (API_ENCRYPT_ALGORITHM.toUpperCase() === 'AES') {
|
|
||||||
if (!API_ENCRYPT_RESPONSE_KEY) {
|
|
||||||
throw new Error('AES 响应解密密钥未配置');
|
|
||||||
}
|
|
||||||
decryptedData = AES.decrypt(encryptedData, API_ENCRYPT_RESPONSE_KEY);
|
|
||||||
} else if (API_ENCRYPT_ALGORITHM.toUpperCase() === 'RSA') {
|
|
||||||
if (!API_ENCRYPT_RESPONSE_KEY) {
|
|
||||||
throw new Error('RSA 私钥未配置');
|
|
||||||
}
|
|
||||||
decryptedData = RSA.decrypt(encryptedData, API_ENCRYPT_RESPONSE_KEY);
|
|
||||||
if (decryptedData === false) {
|
|
||||||
throw new Error('RSA 解密失败');
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw new Error(`不支持的解密算法: ${API_ENCRYPT_ALGORITHM}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!decryptedData) {
|
|
||||||
throw new Error('解密结果为空');
|
|
||||||
}
|
|
||||||
|
|
||||||
// 尝试解析为 JSON,如果失败则返回原字符串
|
|
||||||
try {
|
|
||||||
return JSON.parse(decryptedData);
|
|
||||||
} catch {
|
|
||||||
return decryptedData;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('响应数据解密失败:', error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
};
|
|
|
@ -83,8 +83,10 @@
|
||||||
"@tanstack/vue-store": "catalog:",
|
"@tanstack/vue-store": "catalog:",
|
||||||
"@vue/shared": "catalog:",
|
"@vue/shared": "catalog:",
|
||||||
"clsx": "catalog:",
|
"clsx": "catalog:",
|
||||||
|
"crypto-js": "catalog:",
|
||||||
"dayjs": "catalog:",
|
"dayjs": "catalog:",
|
||||||
"defu": "catalog:",
|
"defu": "catalog:",
|
||||||
|
"jsencrypt": "catalog:",
|
||||||
"lodash.clonedeep": "catalog:",
|
"lodash.clonedeep": "catalog:",
|
||||||
"lodash.get": "catalog:",
|
"lodash.get": "catalog:",
|
||||||
"lodash.isequal": "catalog:",
|
"lodash.isequal": "catalog:",
|
||||||
|
|
|
@ -6,18 +6,6 @@ import { JSEncrypt } from 'jsencrypt';
|
||||||
* 支持 AES 和 RSA 加密算法
|
* 支持 AES 和 RSA 加密算法
|
||||||
*/
|
*/
|
||||||
|
|
||||||
// 从环境变量获取配置
|
|
||||||
const API_ENCRYPT_ENABLE =
|
|
||||||
import.meta.env.VITE_APP_API_ENCRYPT_ENABLE === 'true';
|
|
||||||
const API_ENCRYPT_HEADER =
|
|
||||||
import.meta.env.VITE_APP_API_ENCRYPT_HEADER || 'X-Api-Encrypt';
|
|
||||||
const API_ENCRYPT_ALGORITHM =
|
|
||||||
import.meta.env.VITE_APP_API_ENCRYPT_ALGORITHM || 'AES';
|
|
||||||
const API_ENCRYPT_REQUEST_KEY =
|
|
||||||
import.meta.env.VITE_APP_API_ENCRYPT_REQUEST_KEY || ''; // AES密钥 或 RSA公钥
|
|
||||||
const API_ENCRYPT_RESPONSE_KEY =
|
|
||||||
import.meta.env.VITE_APP_API_ENCRYPT_RESPONSE_KEY || ''; // AES密钥 或 RSA私钥
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* AES 加密工具类
|
* AES 加密工具类
|
||||||
*/
|
*/
|
||||||
|
@ -146,52 +134,31 @@ export const RSA = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API 加解密配置接口
|
||||||
|
*/
|
||||||
|
export interface ApiEncryptConfig {
|
||||||
|
/** 加密算法 */
|
||||||
|
algorithm: 'AES' | 'RSA';
|
||||||
|
/** 是否启用加解密 */
|
||||||
|
enable: boolean;
|
||||||
|
/** 加密头名称 */
|
||||||
|
header: string;
|
||||||
|
/** 请求加密密钥(AES密钥或RSA公钥) */
|
||||||
|
requestKey: string;
|
||||||
|
/** 响应解密密钥(AES密钥或RSA私钥) */
|
||||||
|
responseKey: string;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* API 加解密主类
|
* API 加解密主类
|
||||||
*/
|
*/
|
||||||
export const ApiEncrypt = {
|
export class ApiEncrypt {
|
||||||
/**
|
private config: ApiEncryptConfig;
|
||||||
* 获取加密头名称
|
|
||||||
*/
|
|
||||||
getEncryptHeader(): string {
|
|
||||||
return API_ENCRYPT_HEADER;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
constructor(config: ApiEncryptConfig) {
|
||||||
* 加密请求数据
|
this.config = config;
|
||||||
* @param data 要加密的数据
|
}
|
||||||
* @returns 加密后的数据
|
|
||||||
*/
|
|
||||||
encryptRequest(data: any): string {
|
|
||||||
if (!API_ENCRYPT_ENABLE) {
|
|
||||||
return data;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
const jsonData = typeof data === 'string' ? data : JSON.stringify(data);
|
|
||||||
|
|
||||||
if (API_ENCRYPT_ALGORITHM.toUpperCase() === 'AES') {
|
|
||||||
if (!API_ENCRYPT_REQUEST_KEY) {
|
|
||||||
throw new Error('AES 请求加密密钥未配置');
|
|
||||||
}
|
|
||||||
return AES.encrypt(jsonData, API_ENCRYPT_REQUEST_KEY);
|
|
||||||
} else if (API_ENCRYPT_ALGORITHM.toUpperCase() === 'RSA') {
|
|
||||||
if (!API_ENCRYPT_REQUEST_KEY) {
|
|
||||||
throw new Error('RSA 公钥未配置');
|
|
||||||
}
|
|
||||||
const result = RSA.encrypt(jsonData, API_ENCRYPT_REQUEST_KEY);
|
|
||||||
if (result === false) {
|
|
||||||
throw new Error('RSA 加密失败');
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
} else {
|
|
||||||
throw new Error(`不支持的加密算法: ${API_ENCRYPT_ALGORITHM}`);
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('请求数据加密失败:', error);
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 解密响应数据
|
* 解密响应数据
|
||||||
|
@ -199,27 +166,27 @@ export const ApiEncrypt = {
|
||||||
* @returns 解密后的数据
|
* @returns 解密后的数据
|
||||||
*/
|
*/
|
||||||
decryptResponse(encryptedData: string): any {
|
decryptResponse(encryptedData: string): any {
|
||||||
if (!API_ENCRYPT_ENABLE) {
|
if (!this.config.enable) {
|
||||||
return encryptedData;
|
return encryptedData;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let decryptedData: false | string = '';
|
let decryptedData: false | string = '';
|
||||||
if (API_ENCRYPT_ALGORITHM.toUpperCase() === 'AES') {
|
if (this.config.algorithm.toUpperCase() === 'AES') {
|
||||||
if (!API_ENCRYPT_RESPONSE_KEY) {
|
if (!this.config.responseKey) {
|
||||||
throw new Error('AES 响应解密密钥未配置');
|
throw new Error('AES 响应解密密钥未配置');
|
||||||
}
|
}
|
||||||
decryptedData = AES.decrypt(encryptedData, API_ENCRYPT_RESPONSE_KEY);
|
decryptedData = AES.decrypt(encryptedData, this.config.responseKey);
|
||||||
} else if (API_ENCRYPT_ALGORITHM.toUpperCase() === 'RSA') {
|
} else if (this.config.algorithm.toUpperCase() === 'RSA') {
|
||||||
if (!API_ENCRYPT_RESPONSE_KEY) {
|
if (!this.config.responseKey) {
|
||||||
throw new Error('RSA 私钥未配置');
|
throw new Error('RSA 私钥未配置');
|
||||||
}
|
}
|
||||||
decryptedData = RSA.decrypt(encryptedData, API_ENCRYPT_RESPONSE_KEY);
|
decryptedData = RSA.decrypt(encryptedData, this.config.responseKey);
|
||||||
if (decryptedData === false) {
|
if (decryptedData === false) {
|
||||||
throw new Error('RSA 解密失败');
|
throw new Error('RSA 解密失败');
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`不支持的解密算法: ${API_ENCRYPT_ALGORITHM}`);
|
throw new Error(`不支持的解密算法: ${this.config.algorithm}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!decryptedData) {
|
if (!decryptedData) {
|
||||||
|
@ -236,5 +203,65 @@ export const ApiEncrypt = {
|
||||||
console.error('响应数据解密失败:', error);
|
console.error('响应数据解密失败:', error);
|
||||||
throw error;
|
throw error;
|
||||||
}
|
}
|
||||||
},
|
}
|
||||||
};
|
|
||||||
|
/**
|
||||||
|
* 加密请求数据
|
||||||
|
* @param data 要加密的数据
|
||||||
|
* @returns 加密后的数据
|
||||||
|
*/
|
||||||
|
encryptRequest(data: any): string {
|
||||||
|
if (!this.config.enable) {
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const jsonData = typeof data === 'string' ? data : JSON.stringify(data);
|
||||||
|
|
||||||
|
if (this.config.algorithm.toUpperCase() === 'AES') {
|
||||||
|
if (!this.config.requestKey) {
|
||||||
|
throw new Error('AES 请求加密密钥未配置');
|
||||||
|
}
|
||||||
|
return AES.encrypt(jsonData, this.config.requestKey);
|
||||||
|
} else if (this.config.algorithm.toUpperCase() === 'RSA') {
|
||||||
|
if (!this.config.requestKey) {
|
||||||
|
throw new Error('RSA 公钥未配置');
|
||||||
|
}
|
||||||
|
const result = RSA.encrypt(jsonData, this.config.requestKey);
|
||||||
|
if (result === false) {
|
||||||
|
throw new Error('RSA 加密失败');
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
} else {
|
||||||
|
throw new Error(`不支持的加密算法: ${this.config.algorithm}`);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('请求数据加密失败:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取加密头名称
|
||||||
|
*/
|
||||||
|
getEncryptHeader(): string {
|
||||||
|
return this.config.header;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 创建基于环境变量的 API 加解密实例
|
||||||
|
* @param env 环境变量对象
|
||||||
|
* @returns ApiEncrypt 实例
|
||||||
|
*/
|
||||||
|
export function createApiEncrypt(env: Record<string, any>): ApiEncrypt {
|
||||||
|
const config: ApiEncryptConfig = {
|
||||||
|
enable: env.VITE_APP_API_ENCRYPT_ENABLE === 'true',
|
||||||
|
header: env.VITE_APP_API_ENCRYPT_HEADER || 'X-Api-Encrypt',
|
||||||
|
algorithm: env.VITE_APP_API_ENCRYPT_ALGORITHM || 'AES',
|
||||||
|
requestKey: env.VITE_APP_API_ENCRYPT_REQUEST_KEY || '',
|
||||||
|
responseKey: env.VITE_APP_API_ENCRYPT_RESPONSE_KEY || '',
|
||||||
|
};
|
||||||
|
|
||||||
|
return new ApiEncrypt(config);
|
||||||
|
}
|
|
@ -3,6 +3,7 @@ export * from './date';
|
||||||
export * from './diff';
|
export * from './diff';
|
||||||
export * from './dom';
|
export * from './dom';
|
||||||
export * from './download';
|
export * from './download';
|
||||||
|
export * from './encrypt';
|
||||||
export * from './formatNumber';
|
export * from './formatNumber';
|
||||||
export * from './inference';
|
export * from './inference';
|
||||||
export * from './letter';
|
export * from './letter';
|
||||||
|
|
|
@ -776,18 +776,12 @@ importers:
|
||||||
cropperjs:
|
cropperjs:
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 1.6.2
|
version: 1.6.2
|
||||||
crypto-js:
|
|
||||||
specifier: 'catalog:'
|
|
||||||
version: 4.2.0
|
|
||||||
dayjs:
|
dayjs:
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 1.11.13
|
version: 1.11.13
|
||||||
highlight.js:
|
highlight.js:
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 11.11.1
|
version: 11.11.1
|
||||||
jsencrypt:
|
|
||||||
specifier: 'catalog:'
|
|
||||||
version: 3.5.4
|
|
||||||
pinia:
|
pinia:
|
||||||
specifier: ^3.0.3
|
specifier: ^3.0.3
|
||||||
version: 3.0.3(typescript@5.8.3)(vue@3.5.17(typescript@5.8.3))
|
version: 3.0.3(typescript@5.8.3)(vue@3.5.17(typescript@5.8.3))
|
||||||
|
@ -867,9 +861,6 @@ importers:
|
||||||
cropperjs:
|
cropperjs:
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 1.6.2
|
version: 1.6.2
|
||||||
crypto-js:
|
|
||||||
specifier: 'catalog:'
|
|
||||||
version: 4.2.0
|
|
||||||
dayjs:
|
dayjs:
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 1.11.13
|
version: 1.11.13
|
||||||
|
@ -879,9 +870,6 @@ importers:
|
||||||
highlight.js:
|
highlight.js:
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 11.11.1
|
version: 11.11.1
|
||||||
jsencrypt:
|
|
||||||
specifier: 'catalog:'
|
|
||||||
version: 3.5.4
|
|
||||||
pinia:
|
pinia:
|
||||||
specifier: ^3.0.3
|
specifier: ^3.0.3
|
||||||
version: 3.0.3(typescript@5.8.3)(vue@3.5.17(typescript@5.8.3))
|
version: 3.0.3(typescript@5.8.3)(vue@3.5.17(typescript@5.8.3))
|
||||||
|
@ -1377,12 +1365,18 @@ importers:
|
||||||
clsx:
|
clsx:
|
||||||
specifier: ^2.1.1
|
specifier: ^2.1.1
|
||||||
version: 2.1.1
|
version: 2.1.1
|
||||||
|
crypto-js:
|
||||||
|
specifier: 'catalog:'
|
||||||
|
version: 4.2.0
|
||||||
dayjs:
|
dayjs:
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 1.11.13
|
version: 1.11.13
|
||||||
defu:
|
defu:
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 6.1.4
|
version: 6.1.4
|
||||||
|
jsencrypt:
|
||||||
|
specifier: 'catalog:'
|
||||||
|
version: 3.5.4
|
||||||
lodash.clonedeep:
|
lodash.clonedeep:
|
||||||
specifier: 'catalog:'
|
specifier: 'catalog:'
|
||||||
version: 4.5.0
|
version: 4.5.0
|
||||||
|
|
Loading…
Reference in New Issue