本篇短文主要总结在项目中使用的第三方请求库axios结合TS实现的二次封装,实现了对请求和响应对具体业务的封装配置,代码粘贴及项目项目相关结构如下
- src目录下新建service目录
- request.ts代码
import type { AxiosRequestConfig, AxiosResponse, AxiosError } from 'axios'
import axios from 'axios'
import notify from './interceptor/notify'
import limiter from './interceptor/limiter'
import urlArgs from './interceptor/url-args'
const instance = axios.create()
instance.interceptors.request.use((request: any) => {
request.headers.Authorization = localStorage.getItem('authorization')
return request
})
instance.interceptors.request.use(urlArgs.request.onFulfilled, undefined)
instance.interceptors.request.use(limiter.request.onFulfilled, undefined)
instance.interceptors.response.use(
notify.response.onFulfilled,
notify.response.onRejected
)
instance.interceptors.response.use(
limiter.response.onFulfilled,
limiter.response.onRejected
)
/**
* @description 先不写类型,因为比较繁琐
*/
export function compose(adapters: any[]) {
return adapters.reduceRight((pre, cur) => cur(pre))
}
//处理返回code为非0的错误
export class CodeNotZeroError extends Error {
code: number
constructor(code: number, message: string) {
super(message)
this.code = code
}
}
//返回结果格式处理
export interface ResultFormat<T = any> {
data: null | T
err: AxiosError | CodeNotZeroError | null
response: AxiosResponse<T> | null
}
//后端返回结果格式处理
export interface BackendResultFormat<T = any> {
code: number
data: T
rows: T
message: string
msg: string
}
//请求配置封装
export interface RequestConfig extends AxiosRequestConfig {
// url接口路径
url: NonNullable<AxiosRequestConfig['url']>
// 接口文字描述
desc?: string
// 成功状态通知
notifyWhenSuccess?: boolean
// 完成状态通知
notifyWhenFailure?: boolean
// 接口限流
limit?: number
// url参数替换
args?: Record<string, any>
}
/**
* 允许定义三个泛型参数:
* Payload为响应数据
* Data为请求体参数,对应config.data
* Params对应URL的请求参数,对应config.params
*/
interface Request {
<Payload = any>(config: RequestConfig): (
requestConfig?: Partial<RequestConfig>
) => Promise<ResultFormat<Payload>>
<Payload, Data>(config: RequestConfig): (
requestConfig: Partial<Omit<RequestConfig, 'data'>> & { data: Data }
) => Promise<ResultFormat<Payload>>
<Payload, Data, Params>(config: RequestConfig): (
requestConfig: Partial<Omit<RequestConfig, 'data' | 'params'>> &
(Data extends undefined ? { data?: undefined } : { data: Data }) & {
params: Params
}
) => Promise<ResultFormat<Payload>>
// 加上如果带Args泛型参数的情况,同样的,如果指定Params或Data泛型参数为undefined,则可忽略不填
<Payload, Data, Params, Args>(config: RequestConfig): (
requestConfig: Partial<Omit<RequestConfig, 'data' | 'params' | 'args'>> &
(Data extends undefined ? { data?: undefined } : { data: Data }) &
(Params extends undefined
? { params?: undefined }
: { params: Params }) & {
args: Args
}
) => Promise<ResultFormat<Payload>>
}
const makeRequest: Request = <T>(config: RequestConfig) => {
return async (requestConfig?: Partial<RequestConfig>) => {
// 合并在service中定义的option和调用时从外部传入的option
const mergedConfig: RequestConfig = {
...config,
...requestConfig,
headers: {
...config.headers,
...requestConfig?.headers
}
}
// 统一处理返回类型
try {
const response: AxiosResponse<BackendResultFormat<T>> =
await instance.request<BackendResultFormat<T>>(mergedConfig)
const res = response.data
const host = window.location.hostname
if (res.code !== 0 && res.code !== 200) {
if (res.code == 1308) {
window.location.assign(`http://${host}/#/login/?setting=${1}`)
}
const error = new CodeNotZeroError(
res.code,
res.message ? res.message : res.msg
)
return { err: error, data: res.data, response }
}
return { err: null, data: res.data || res.rows, response }
} catch (err: any) {
return { err, data: null, response: null }
}
}
}
export default makeRequest
3.建立interceptor文件夹,并建立如下文件,主要用于对请求和响应的业务逻辑处理
4.gateway.ts代码,主要用于在开发环境下需要有多个代理的情况下进行相关处理
import type { AxiosRequestConfig } from 'axios'
// 代码运行环境
const env: any = process.env.NODE_ENV || 'development'
// 服务的环境类
const SERVERS: Record<
string,
(env: string, request: { url?: string }) => string
> = {
cpi(env, request) {
return env == 'development' ? `${request.url}` : `${request.url}`
},
dpi(env, request) {
return env == 'development' ? `${request.url}` : `${request.url}`
}
}
export default {
request: {
onFulfilled: (config: AxiosRequestConfig) => {
const path = config.url?.split('?')
//@ts-ignore
config.headers.Authorization = localStorage.getItem('authorization') || ''
const TYPE = (path as string[])[0].split('/')[1]
// TODO: /api/api
config.url = SERVERS[TYPE]
? SERVERS[TYPE](env, config)
: env == 'development'
? `/api/api${config.url}`
: `/api${config.url}`
return config
}
}
}
5.limiter.ts,主要用于在定义limit时执行限流逻辑
import { BackendResultFormat, RequestConfig } from '@/service/request'
import { AxiosError, AxiosRequestConfig, AxiosResponse } from 'axios'
import gateway from '@/service/interceptor/gateway'
type ResolveFn = (value: unknown) => void
const records: Record<string, { count: number; queue: ResolveFn[] }> = {}
const generateKey = (config: RequestConfig) => `${config.url}-${config.method}`
export default {
request: {
onFulfilled: async (config: AxiosRequestConfig) => {
config = gateway.request.onFulfilled(config) as RequestConfig
const { limit } = config as RequestConfig
// 如果limit被定义,则执行限流逻辑
if (typeof limit === 'number') {
const key = generateKey(config as RequestConfig)
if (!records[key]) {
records[key] = { count: 0, queue: [] }
}
console.log(key, records, config, 'record1')
const record = records[key]
record.count += 1
if (record.count <= limit) {
return config
}
// 把该请求通过await阻塞存储在queue队列中
await new Promise((resolve) => {
record.queue.push(resolve)
})
return config
}
return config
}
},
response: {
onFulfilled: (response: AxiosResponse<BackendResultFormat>) => {
const config = response.config as RequestConfig
const { limit } = config
if (typeof limit === 'number') {
const key = generateKey(config)
const record = records[key]
console.log(key, records, response, 'record2')
record.count -= 1
if (record.queue.length) {
record.queue.shift()!(null)
}
}
return response
},
onRejected: (error: AxiosError<BackendResultFormat>) => {
// const config = error.config
// const { limit } = config as RequestConfig
// if (typeof limit === 'number') {
// const key = generateKey(config)
// const record = records[key]
// record.count -= 1
// if (record.queue.length) {
// record.queue.shift()!(null)
// }
// }
return error
}
}
}
6.notify.ts,主要用于请求响应业务处理
import type { AxiosError, AxiosResponse } from 'axios'
import type { BackendResultFormat, RequestConfig } from '@/service/request'
export default {
response: {
onFulfilled: (response: AxiosResponse<BackendResultFormat>) => {
const { code, message, msg } = response.data
const { desc, notifyWhenFailure, notifyWhenSuccess, method } =
response.config as RequestConfig
// 如果desc被定义,则执行反馈逻辑
if (desc) {
// 对code为0的响应做成功反馈
if (code === 0 || code === 200) {
if (notifyWhenSuccess !== false) {
if (
['delete', 'put', 'post'].includes(
method?.toLocaleLowerCase() || ''
) ||
notifyWhenSuccess === true
) {
window.$message.success(`${desc}成功`)
}
}
} else if (notifyWhenFailure !== false) {
window.$message.error(`${desc}错误~原因:${message ? message : msg}`)
}
}
return response
},
onRejected: (error: AxiosError<BackendResultFormat>) => {
const { response, config } = error
// 对4xx,5xx状态码做失败反馈
const { url, desc } = config as RequestConfig
if (desc) {
if (response?.status && response.statusText) {
window.$message.error(
`状态:${response.status}~${response.statusText}~路径:${url}~${
response.data?.message ? response.data?.message : ''
}`
)
} else {
// 处理请求响应失败,例如网络offline,超时等做失败反馈
window.$message.error(`原因:${error.message}~路径:${url}`)
}
}
return error
}
}
}
7.url-args.ts文件,请求为path传参时的业务处理
import { AxiosRequestConfig } from 'axios'
import { RequestConfig } from '@/service/request'
// import { ElNotification } from 'element-plus'
export default {
request: {
onFulfilled: (config: AxiosRequestConfig) => {
const { url, args } = config as RequestConfig
// 如果args被定义,则执行路径参数替换逻辑
if (args) {
const lostParams: string[] = []
const replaceUrl = url.replace(/{([^}]+)}/g, (res, arg: string) => {
if (!args[arg]) {
lostParams.push(arg)
}
return args[arg] as string
})
if (lostParams.length) {
// ElNotification({
// type: 'error',
// message: `
// <div>
// <div>内容:在args中找不到
// <span>
// ${lostParams.map((arg) => arg)}
// </span>
// 属性
// </div>
// <div>路径:${url}</div>
// </div>
// `,
// dangerouslyUseHTMLString: true
// })
return Promise.reject(new Error('在args中找不到对应的路径参数'))
}
return { ...config, url: replaceUrl }
}
return config
}
}
}
使用示例:
export const apiOpenNetworkConfig = makeRequest({
url: '/base/network/open/{id}',
method: 'post',
desc: '启用配置',
notifyWhenSuccess: true,
notifyWhenFailure: true
})
至此所有的代码粘贴完毕了,也是第一次在项目中遇到有这块封装处理在,因此总结到此篇文章中,如果有任何疑问和补充欢迎大家在评论补充