2025-12-31 06:47:39 -08:00

281 lines
8.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import type { AxiosInstance, AxiosRequestConfig, AxiosResponse } from 'axios'
import axios from 'axios'
import { AxiosCanceler } from './cancel'
import router from '@/router'
import { toast } from 'vue-sonner'
import { HiAesUtil } from './HiAesUtil.ts'
const encryptionKey = 'c0qhq99a-nq8h-ropg-wrlc-ezj4dlkxqpzx'
function redirectLogin() {
localStorage.removeItem('Authorization')
router.push({ path: '/', query: { login: 'true' } })
}
export interface ExtraConfig {
/**
* 默认值 1 错误等级 0-忽略1-warning2-error
*/
errorLevel?: 0 | 1 | 2
/**
* 默认true 是否携带token
*/
withToken?: boolean
/**
* 默认true 是否处理响应
*/
handleResponse?: boolean
/**
* 默认false 是否取消重复请求
*/
cancelRepetition?: boolean
/**
* 默认false 是否返回axios完整响应
*/
originResponseData?: boolean
/**
* 默认token 存储key
*/
tokenKey?: string
/**
* 获取token的方法
*/
getToken?: () => string
/**
* 自定义header方法此方法返回的header会覆盖默认的header
*/
formatHeader?: (header: Record<string, string>) => Record<string, string>
}
export interface RequestConfig extends AxiosRequestConfig {
extraConfig?: ExtraConfig
}
interface ResponseType extends AxiosResponse {
config: RequestConfig
}
const ERROR_MESSAGES: Record<number | string, string> = {
'200': '成功',
'500': '内部服务器错误',
'10001': '数据库查询错误',
'10002': '数据库更新错误',
'10003': '数据库插入错误',
'10004': '数据库删除错误',
'20001': '用户已存在',
'20002': '用户不存在',
'20003': '用户密码错误',
'20004': '用户已禁用',
'20005': '余额不足',
'20006': '停止注册',
'20007': '未绑定Telegram',
'20008': '用户未绑定OAuth方式',
'20009': '邀请码错误',
'30001': '节点已存在',
'30002': '节点不存在',
'30003': '节点组已存在',
'30004': '节点组不存在',
'30005': '节点组不为空',
'400': '参数错误',
'40002': '用户令牌为空',
'40003': '用户令牌无效',
'40004': '用户令牌已过期',
'40005': '您还没有登录',
'401': '请求过多',
'50001': '优惠券不存在',
'50002': '优惠券已被使用',
'50003': '优惠券不匹配',
'60001': '订阅已过期',
'60002': '订阅不可用',
'60003': '用户已有订阅',
'60004': '订阅已被使用',
'60005': '单一订阅模式超出限制',
'60006': '订阅配额限制',
'70001': '验证码错误',
'80001': '队列入队错误',
'90001': '调试模式已启用',
'90002': '发送短信错误',
'90003': '短信功能未启用',
'90004': '电子邮件功能未启用',
'90005': '不支持的登录方式',
'90006': '身份验证器不支持此方式',
'90007': '电话区号为空',
'90008': '密码为空',
'90009': '区号为空',
'90010': '需要密码或验证码',
'90011': '电子邮件已存在',
'90012': '电话号码已存在',
'90013': '设备已存在',
'90014': '电话号码错误',
'90015': '此账户今日已达到发送次数限制',
'90017': '设备不存在',
'90018': '用户 ID 不匹配',
'61001': '订单不存在',
'61002': '支付方式未找到',
'61003': '订单状态错误',
'61004': '重置周期不足',
'61005': '存在没用完的流量',
}
export default class Request {
public axiosInstance: AxiosInstance
private config: RequestConfig
constructor(config: RequestConfig) {
this.config = config
this.axiosInstance = axios.create(config)
this.init()
}
static defaultConfig: Required<ExtraConfig> = {
errorLevel: 2,
withToken: true,
handleResponse: true,
cancelRepetition: false,
originResponseData: false,
tokenKey: 'token',
formatHeader: (headers) => {
return headers
},
getToken: () => '',
}
private errorReport(lv: number, message: string) {
toast(message)
}
private init() {
const axiosCanceler = new AxiosCanceler()
this.axiosInstance.interceptors.request.use(
(config: RequestConfig) => {
const mergeExtraConfig = {
...Request.defaultConfig,
...this.config.extraConfig,
...config.extraConfig,
}
if (config.data && !(config.data instanceof FormData)) {
const plainText = JSON.stringify(config.data)
config.data = HiAesUtil.encryptData(plainText, encryptionKey)
}
if (config.method?.toLowerCase() === 'get' || config.params) {
const paramsToEncrypt = config.params || {}
const plainParamsText = JSON.stringify(paramsToEncrypt)
const encryptedParams = HiAesUtil.encryptData(plainParamsText, encryptionKey)
config.params = {
data: encryptedParams.data,
time: encryptedParams.time,
}
}
config.headers = mergeExtraConfig.formatHeader({
...this.config.headers,
...config.headers,
lang: 'zh_CN',
'login-type': 'device',
'user-agent': 'android',
...(mergeExtraConfig.withToken && {
[mergeExtraConfig.tokenKey]: mergeExtraConfig.getToken(),
}),
} as any)
config.extraConfig = mergeExtraConfig
if (mergeExtraConfig.cancelRepetition) {
axiosCanceler.addPending(config)
}
return config
},
(error) => {
this.errorReport(error?.config.extraConfig.errorLevel, error)
return Promise.reject(error)
},
)
this.axiosInstance.interceptors.response.use(
(response: ResponseType) => {
const { data, config } = response
let responseData = response.data.data
if (responseData && responseData.data && responseData.time) {
try {
const decryptedStr = HiAesUtil.decryptData(
responseData.data,
responseData.time,
encryptionKey,
)
responseData = JSON.parse(decryptedStr)
} catch (e) {
console.error('解密失败:', e)
return Promise.reject({ message: '数据解密异常' })
}
}
axiosCanceler.removePending(config)
if (data.code !== 200) {
const msg = ERROR_MESSAGES[data.code] || response.data?.msg || data?.error || '未知错误'
if (data.code == 40004 || data.code == 40003 || data.code == 40005) {
toast.error(msg)
redirectLogin()
return
}
if (config.extraConfig?.handleResponse) {
this.errorReport(config.extraConfig.errorLevel ?? 2, msg)
return Promise.reject({
...data,
message: msg,
})
}
}
return config.extraConfig?.originResponseData ? response : responseData
},
(error) => {
const status = error?.response?.status
const code = error?.code
let message = error?.message
if (status === 401) {
redirectLogin()
return
}
if (code === 'ECONNABORTED') {
message = '网络环境太差,请求超时'
} else if (code === 'Network Error' || message === 'Network Error') {
if (error.response) {
message = `${error.response.status}:network连接失败请求中断`
} else {
message = '网络好像出现问题了'
}
}
if (error.__CANCEL__) {
console.warn('request canceled', error?.message)
} else {
this.errorReport(error?.config?.extraConfig?.errorLevel, message)
}
return Promise.reject(error)
},
)
}
public get<T>(url: string, params?: Record<string, unknown>, config?: RequestConfig): Promise<T> {
return this.axiosInstance.get(url, { ...config, params })
}
public post<D, T>(url: string, data?: D, config?: RequestConfig): Promise<T> {
return this.axiosInstance.post(url, data, { ...config })
}
public put<D, T>(url: string, data?: D, config?: RequestConfig): Promise<T> {
return this.axiosInstance.put(url, data, { ...config })
}
public patch<D, T>(url: string, data?: D, config?: RequestConfig): Promise<T> {
return this.axiosInstance.patch(url, data, { ...config })
}
public delete<D, T>(url: string, params?: D, config?: RequestConfig): Promise<T> {
return this.axiosInstance.delete(url, { ...config, params })
}
}