✨ add api signature
This commit is contained in:
@@ -5,14 +5,14 @@ import useStore from "@/store";
|
||||
import {localforageStorageAdapter} from "@/utils/alova/adapter/localforageStorageAdapter.ts";
|
||||
import {createServerTokenAuthentication} from "alova/client";
|
||||
import {AxiosError, AxiosResponse} from "axios";
|
||||
import {handleCode} from "@/utils/errorCode/errorCodeHandler.ts";
|
||||
import {message, Modal} from "ant-design-vue";
|
||||
import i18n from "@/locales";
|
||||
import {axiosRequestAdapter} from "@alova/adapter-axios";
|
||||
import {refreshToken} from "@/api/user";
|
||||
import createMD5Signature, {generateNonce} from "@/utils/signature/signature.ts";
|
||||
import generateKeySecretSignature from "@/utils/signature/signature.ts";
|
||||
import {handleErrorCode} from "@/utils/errorCode/errorCodeHandler.ts";
|
||||
|
||||
|
||||
let hasShownNetworkError: boolean = false;
|
||||
const {onAuthRequired, onResponseRefreshToken} = createServerTokenAuthentication<typeof VueHook,
|
||||
typeof axiosRequestAdapter>({
|
||||
refreshTokenOnSuccess: {
|
||||
@@ -34,7 +34,7 @@ const {onAuthRequired, onResponseRefreshToken} = createServerTokenAuthentication
|
||||
}
|
||||
});
|
||||
export const service = createAlova({
|
||||
timeout: 5000,
|
||||
timeout: 10000,
|
||||
baseURL: import.meta.env.VITE_APP_BASE_API,
|
||||
statesHook: VueHook,
|
||||
// 请求适配器
|
||||
@@ -50,15 +50,9 @@ export const service = createAlova({
|
||||
}
|
||||
const lang = useStore().lang;
|
||||
method.config.headers['Accept-Language'] = lang.lang || 'zh';
|
||||
// 添加签名
|
||||
if (method.type === 'POST') {
|
||||
const nonce: string = generateNonce(); // 生成随机的 Nonce
|
||||
const {signature, timestamp}: { signature: string, timestamp: number } = createMD5Signature(method, nonce);
|
||||
method.config.headers['X-Sign'] = signature;
|
||||
method.config.headers['X-Timestamp'] = timestamp;
|
||||
method.config.headers['X-Nonce'] = nonce;
|
||||
if (method.meta?.signature) {
|
||||
method.config.headers['X-Content-Security'] = generateKeySecretSignature(0, method.type, method.url, method.config.params, method.data);
|
||||
}
|
||||
|
||||
}),
|
||||
// 响应拦截器
|
||||
responded: onResponseRefreshToken({
|
||||
@@ -77,11 +71,6 @@ export const service = createAlova({
|
||||
window.location.href = '/login';
|
||||
}, 1000);
|
||||
},
|
||||
// onCancel() {
|
||||
// setTimeout(() => {
|
||||
// window.location.href = '/login';
|
||||
// },2000);
|
||||
// }
|
||||
});
|
||||
return Promise.reject(response.data);
|
||||
}
|
||||
@@ -92,9 +81,8 @@ export const service = createAlova({
|
||||
onError:
|
||||
(error: AxiosError, _method: any) => {
|
||||
const {response} = error;
|
||||
if (response && !hasShownNetworkError) {
|
||||
hasShownNetworkError = true;
|
||||
handleCode(response.status);
|
||||
if (response) {
|
||||
handleErrorCode(response.status);
|
||||
}
|
||||
if (!window.navigator.onLine) {
|
||||
message.error(i18n.global.t('error.networkError')).then();
|
||||
|
@@ -1,64 +0,0 @@
|
||||
/** @format */
|
||||
|
||||
import axios, {AxiosInstance, AxiosRequestConfig} from "axios";
|
||||
import {message} from "ant-design-vue";
|
||||
import useStore from "@/store";
|
||||
import {handleCode} from "@/utils/errorCode/errorCodeHandler.ts";
|
||||
|
||||
class Request {
|
||||
private instance: AxiosInstance | undefined;
|
||||
|
||||
constructor(config: AxiosRequestConfig) {
|
||||
this.instance = axios.create(config);
|
||||
// 全局请求拦截
|
||||
this.instance.interceptors.request.use(
|
||||
(config) => {
|
||||
const user = useStore().user;
|
||||
const token: string | undefined = user.user.accessToken;
|
||||
if (token) {
|
||||
config.headers.Authorization = `${import.meta.env.VITE_APP_TOKEN_KEY} ${token}`;
|
||||
}
|
||||
return config;
|
||||
},
|
||||
(error) => {
|
||||
return Promise.reject(error);
|
||||
},
|
||||
);
|
||||
|
||||
// 全局响应拦截
|
||||
this.instance.interceptors.response.use(
|
||||
(response) => {
|
||||
if (response.data instanceof Blob) {
|
||||
return response;
|
||||
} else {
|
||||
return response.data;
|
||||
}
|
||||
},
|
||||
(error) => {
|
||||
const {response} = error;
|
||||
if (response) {
|
||||
handleCode(response.status);
|
||||
}
|
||||
if (!window.navigator.onLine) {
|
||||
message.error("网络连接失败");
|
||||
return Promise.reject(error);
|
||||
}
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
request<T>(config: AxiosRequestConfig<T>): Promise<T> {
|
||||
return new Promise<T>((resolve, reject) => {
|
||||
this.instance
|
||||
?.request<T, T>(config)
|
||||
.then((res) => {
|
||||
resolve(res);
|
||||
})
|
||||
.catch((err) => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export default Request;
|
@@ -1,10 +0,0 @@
|
||||
/** @format */
|
||||
|
||||
import Request from "./request";
|
||||
|
||||
const web: Request = new Request({
|
||||
baseURL: import.meta.env.VITE_APP_BASE_API,
|
||||
timeout: 5000,
|
||||
});
|
||||
|
||||
export default web;
|
@@ -1,6 +1,6 @@
|
||||
import {message} from "ant-design-vue";
|
||||
import i18n from "@/locales";
|
||||
export function handleCode(code: number): void {
|
||||
export function handleErrorCode(code: number): void {
|
||||
switch (code) {
|
||||
case 400:
|
||||
message
|
||||
|
@@ -1,33 +1,52 @@
|
||||
import CryptoJS from 'crypto-js';
|
||||
import JSEncrypt from 'jsencrypt';
|
||||
|
||||
const rsaPublicKey = "-----BEGIN PUBLIC KEY-----" +
|
||||
"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCFe70Zi3OF7NuFi2saenJPjADW" +
|
||||
"Ln402d142LOLBeN6cuWpItE3qgFsaMSorQApSM0recmAHMg4M4ly7+NgFPsaTzte" +
|
||||
"MrO/LFCagwLWyyFJeqV4oQWRNQcFcGev8sTkUbIhhKpNAcmg37q8cmfI2eumycfl" +
|
||||
"2FXuSyoJOa7hJgYNNQIDAQAB" +
|
||||
"-----END PUBLIC KEY-----";
|
||||
|
||||
/**
|
||||
* 生成 MD5 签名
|
||||
* @param method
|
||||
* @param nonce
|
||||
* 生成前端密钥
|
||||
* @param {string} type 请求类型(0或1)
|
||||
* @param {string} method 请求方法(大写)
|
||||
* @param {string} reqPath 请求路径(不包含host)
|
||||
* @param {Object} reqQuery 请求参数
|
||||
* @param {string} reqBody 请求体
|
||||
*/
|
||||
export default function createMD5Signature(method: any, nonce: string) {
|
||||
const secretKey: string = "38h0ex04du8qqf9ar2knn1quicdsm4s0"; // 密钥
|
||||
const timestamp: number = Date.now(); // 获取当前时间戳
|
||||
const payload: string = JSON.stringify(method.data || {}); // 获取请求数据
|
||||
export default function generateKeySecretSignature(type: number, method: string, reqPath: string, reqQuery: any, reqBody: any): string {
|
||||
const time = (Date.now() / 1000).toFixed();
|
||||
const fingerprint = import.meta.env.VITE_FINGERPRINT_KEY as string;
|
||||
// 生成 secret
|
||||
const base64Key = btoa(fingerprint);
|
||||
const secret = `type=${type};key=${base64Key};time=${time}`;
|
||||
|
||||
// 创建待签名字符串
|
||||
const baseString: string = `${method.type}:${payload}:${timestamp}:${nonce}:${secretKey}`;
|
||||
const encryptor = new JSEncrypt();
|
||||
encryptor.setPublicKey(rsaPublicKey);
|
||||
const encryptedSecret = encryptor.encrypt(secret);
|
||||
|
||||
// 生成 MD5 签名
|
||||
const signature: string = CryptoJS.MD5(baseString).toString();
|
||||
// 生成 signature
|
||||
const sha256Hash = CryptoJS.SHA256(typeof reqBody === 'object' ? JSON.stringify(reqBody) : reqBody).toString();
|
||||
|
||||
// 你可以根据需要返回包含时间戳的签名对象
|
||||
return {
|
||||
signature,
|
||||
timestamp,
|
||||
nonce
|
||||
};
|
||||
const signContent = [
|
||||
time,
|
||||
method,
|
||||
reqPath,
|
||||
isEmptyObject(reqQuery) ? '' : JSON.stringify(reqQuery),
|
||||
sha256Hash
|
||||
].join('\n');
|
||||
|
||||
const hmacHash = CryptoJS.HmacSHA256(signContent, fingerprint);
|
||||
const signature = CryptoJS.enc.Base64.stringify(hmacHash);
|
||||
|
||||
return `key=${fingerprint};secret=${encryptedSecret};signature=${signature}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成随机字符串作为 nonce
|
||||
* 判断对象是否为空
|
||||
* @param obj
|
||||
*/
|
||||
export function generateNonce() {
|
||||
return Math.random().toString(36).substring(2, 16); // 生成16位随机字符串
|
||||
}
|
||||
|
||||
const isEmptyObject = (obj: any): boolean => {
|
||||
return obj && Object.keys(obj).length === 0 && obj.constructor === Object;
|
||||
};
|
||||
|
Reference in New Issue
Block a user