281 lines
8.3 KiB
TypeScript
281 lines
8.3 KiB
TypeScript
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-warning,2-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 })
|
||
}
|
||
}
|