Browse Source

refactor(alova): 重构 AlovaClient 类并添加泛型支持

- 为 AlovaClient 类添加泛型参数 T,默认为 AlovaGenerics
- 重构请求拦截器和响应拦截器的类型定义
- 新增 ResponseData接口定义标准响应数据结构- 新增 ResponseSuccessMethod 类型定义请求成功拦截器方法
- 优化构造函数,支持传入自定义认证选项
- 为类方法添加 JSDoc 注释,提高代码可读性
- 更新 pnpm-lock.yaml,添加 alova 依赖
alova
Jin Mao 6 months ago
parent
commit
34c73b73d9
  1. 292
      packages/effects/plugins/src/alova/index.ts
  2. 25
      pnpm-lock.yaml

292
packages/effects/plugins/src/alova/index.ts

@ -9,167 +9,267 @@ import type {
RespondedAlovaGenerics, RespondedAlovaGenerics,
ResponseCompleteHandler, ResponseCompleteHandler,
ResponseErrorHandler, ResponseErrorHandler,
StatesHook,
} from 'alova'; } from 'alova';
import type { import type {
AlovaRequestAdapterUnified, AlovaRequestAdapterUnified,
ClientTokenAuthenticationOptions, TokenAuthenticationResult,
} from 'alova/client'; } from 'alova/client';
import { createAlova } from 'alova'; import { createAlova } from 'alova';
import { createServerTokenAuthentication } from 'alova/client';
import adapterFetch from 'alova/fetch'; import adapterFetch from 'alova/fetch';
import VueHook from 'alova/vue'; import VueHook from 'alova/vue';
type RequestMethod = (method: Method<AlovaGenerics>) => Promise<void> | void; /**
*
*/
type RequestMethod<T extends AlovaGenerics = AlovaGenerics> = (
method: Method<T>,
) => Promise<void> | void;
class AlovaClient { /**
public readonly instance: Alova<AlovaGenerics>; *
*/
interface ResponseData<T = unknown> {
code?: number;
data?: T;
message?: string;
[key: string]: unknown;
}
/**
*
*/
type ResponseSuccessMethod<T = unknown> = (
json: ResponseData<T>,
) => Promise<ResponseData<T>>;
/**
* Alova HTTP客户端封装类
* @template T AlovaGenerics
*/
class AlovaClient<T extends AlovaGenerics = AlovaGenerics> {
public readonly instance: Alova<T>;
/**
*
*/
public requestInterceptor: RequestMethod<T>[] = [];
/**
*
*/
public responseCompleteInterceptor: ResponseCompleteHandler<T>[] = [];
/**
*
*/
public responseErrorInterceptor: ResponseErrorHandler<T>[] = [];
public requestInterceptor: RequestMethod[] = []; /**
public responseCompleteInterceptor: ResponseCompleteHandler<AlovaGenerics>[] = *
[]; */
public responseErrorInterceptor: ResponseErrorHandler<AlovaGenerics>[] = []; public responseSuccessInterceptor: ResponseSuccessMethod[] = [];
// public responseSuccessInterceptor: RespondedHandler<AlovaGenerics>[] = []; /**
public responseSuccessInterceptor: (( *
Json: Record<string, any>, * @param options Alova配置选项
) => Promise<Record<string, any>>)[] = []; * @param authOptions
*/
constructor( constructor(
options: AlovaOptions<AlovaGenerics>, options: AlovaOptions<T>,
tokenOptions?: ClientTokenAuthenticationOptions<AlovaRequestAdapterUnified>, authOptions: TokenAuthenticationResult<
StatesHook<any>,
AlovaRequestAdapterUnified
> = {},
) { ) {
const { baseURL = '' } = options; const { onAuthRequired, onResponseRefreshToken } = authOptions;
// const { onAuthRequired, onResponseRefreshToken } = const beforeRequest = async (method: Method<T>) => {
// createClientTokenAuthentication<typeof VueHook>({ for (const interceptor of this.requestInterceptor) {
// // ... await interceptor(method);
// ...tokenOptions, }
// }); };
const { onAuthRequired, onResponseRefreshToken } = const responded = {
createServerTokenAuthentication<typeof VueHook>({ // 请求成功的拦截器
// ... // 当使用 `alova/fetch` 请求适配器时,第一个参数接收Response对象
...tokenOptions, // 第二个参数为当前请求的method实例,你可以用它同步请求前后的配置信息
}); onSuccess: async (response: Response, method: Method<T>) => {
if (response.status >= 400) {
throw new Error(response.statusText);
}
const json = await response.json();
let result = json;
for (const interceptor of this.responseSuccessInterceptor) {
result = await interceptor(json);
}
return result;
},
onError: async (err: Error, method: Method<T>) => {
for (const interceptor of this.responseErrorInterceptor) {
await interceptor(err, method);
}
},
// 请求完成的拦截器
// 当你需要在请求不论是成功、失败、还是命中缓存都需要执行的逻辑时,可以在创建alova实例时指定全局的`onComplete`拦截器,例如关闭请求 loading 状态。
// 接收当前请求的method实例
onComplete: async (method: Method<T>) => {
// 处理请求完成逻辑
for (const interceptor of this.responseCompleteInterceptor) {
await interceptor(method);
}
},
};
this.instance = createAlova({ this.instance = createAlova({
baseURL, baseURL: '',
requestAdapter: adapterFetch(), requestAdapter: adapterFetch(),
statesHook: VueHook, statesHook: VueHook,
beforeRequest: onAuthRequired(async (method) => { beforeRequest: onAuthRequired
for (const interceptor of this.requestInterceptor) { ? onAuthRequired(beforeRequest)
await interceptor(method); : beforeRequest,
}
}),
// 使用 responded 对象分别指定请求成功的拦截器和请求失败的拦截器 // 使用 responded 对象分别指定请求成功的拦截器和请求失败的拦截器
responded: onResponseRefreshToken({ responded: onResponseRefreshToken
// 请求成功的拦截器 ? onResponseRefreshToken(responded)
// 当使用 `alova/fetch` 请求适配器时,第一个参数接收Response对象 : responded,
// 第二个参数为当前请求的method实例,你可以用它同步请求前后的配置信息 ...options,
onSuccess: async (response, method) => {
if (response.status >= 400) {
throw new Error(response.statusText);
}
const json = await response.json();
let result;
for (const interceptor of this.responseSuccessInterceptor) {
result = await interceptor(json);
}
return result;
},
onError: async (err, method) => {
console.log(222);
for (const interceptor of this.responseErrorInterceptor) {
await interceptor(err, method);
}
},
// 请求完成的拦截器
// 当你需要在请求不论是成功、失败、还是命中缓存都需要执行的逻辑时,可以在创建alova实例时指定全局的`onComplete`拦截器,例如关闭请求 loading 状态。
// 接收当前请求的method实例
onComplete: async (method) => {
// 处理请求完成逻辑
for (const interceptor of this.responseCompleteInterceptor) {
await interceptor(method);
}
},
}),
}); });
} }
public addRequestInterceptor( /**
method: (method: Method<AlovaGenerics>) => Promise<void> | void, *
) { * @param method
*/
public addRequestInterceptor(method: RequestMethod<T>) {
this.requestInterceptor.push(method); this.requestInterceptor.push(method);
} }
public addResponseCompleteInterceptor( /**
method: ResponseCompleteHandler<AlovaGenerics>, *
) { * @param method
*/
public addResponseCompleteInterceptor(method: ResponseCompleteHandler<T>) {
this.responseCompleteInterceptor.push(method); this.responseCompleteInterceptor.push(method);
} }
public addResponseErrorInterceptor( /**
method: ResponseErrorHandler<AlovaGenerics>, *
) { * @param method
*/
public addResponseErrorInterceptor(method: ResponseErrorHandler<T>) {
this.responseErrorInterceptor.push(method); this.responseErrorInterceptor.push(method);
} }
public addResponseSuccessInterceptor( /**
method: (Json: Record<string, any>) => Promise<Record<string, any>>, *
) { * @param method
*/
public addResponseSuccessInterceptor(method: ResponseSuccessMethod) {
this.responseSuccessInterceptor.push(method); this.responseSuccessInterceptor.push(method);
} }
/**
* DELETE请求
* @param url
* @param data
* @param config
* @returns Method实例
*/
public delete<Responded = unknown, Transformed = unknown>( public delete<Responded = unknown, Transformed = unknown>(
url: string, url: string,
data?: RequestBody, data?: RequestBody,
config?: AlovaMethodCreateConfig<AlovaGenerics, Responded, Transformed>, config?: AlovaMethodCreateConfig<T, Responded, Transformed>,
): Method<RespondedAlovaGenerics<AlovaGenerics, Responded, Transformed>> { ): Method<RespondedAlovaGenerics<T, Responded, Transformed>> {
return this.instance.Delete(url, config, data); return this.instance.Delete(url, config, data);
} }
/**
* GET请求
* @param url
* @param config
* @returns Method实例
*/
public get<Responded = unknown, Transformed = unknown>( public get<Responded = unknown, Transformed = unknown>(
url: string, url: string,
config?: AlovaMethodCreateConfig<AlovaGenerics, Responded, Transformed>, config?: AlovaMethodCreateConfig<T, Responded, Transformed>,
): Method<RespondedAlovaGenerics<AlovaGenerics, Responded, Transformed>> { ): Method<RespondedAlovaGenerics<T, Responded, Transformed>> {
return this.instance.Get(url, config); return this.instance.Get(url, config);
} }
/**
* HEAD请求
* @param url
* @param config
* @returns Method实例
*/
public head<Responded = unknown, Transformed = unknown>( public head<Responded = unknown, Transformed = unknown>(
url: string, url: string,
config?: AlovaMethodCreateConfig<AlovaGenerics, Responded, Transformed>, config?: AlovaMethodCreateConfig<T, Responded, Transformed>,
): Method<RespondedAlovaGenerics<AlovaGenerics, Responded, Transformed>> { ): Method<RespondedAlovaGenerics<T, Responded, Transformed>> {
return this.instance.Head(url, config); return this.instance.Head(url, config);
} }
/**
* OPTIONS请求
* @param url
* @param config
* @returns Method实例
*/
public options<Responded = unknown, Transformed = unknown>( public options<Responded = unknown, Transformed = unknown>(
url: string, url: string,
config?: AlovaMethodCreateConfig<AlovaGenerics, Responded, Transformed>, config?: AlovaMethodCreateConfig<T, Responded, Transformed>,
): Method<RespondedAlovaGenerics<AlovaGenerics, Responded, Transformed>> { ): Method<RespondedAlovaGenerics<T, Responded, Transformed>> {
return this.instance.Options(url, config); return this.instance.Options(url, config);
} }
/**
* PATCH请求
* @param url
* @param data
* @param config
* @returns Method实例
*/
public patch<Responded = unknown, Transformed = unknown>( public patch<Responded = unknown, Transformed = unknown>(
url: string, url: string,
data?: RequestBody, data?: RequestBody,
config?: AlovaMethodCreateConfig<AlovaGenerics, Responded, Transformed>, config?: AlovaMethodCreateConfig<T, Responded, Transformed>,
): Method<RespondedAlovaGenerics<AlovaGenerics, Responded, Transformed>> { ): Method<RespondedAlovaGenerics<T, Responded, Transformed>> {
return this.instance.Patch(url, data, config); return this.instance.Patch(url, data, config);
} }
/**
* POST请求
* @param url
* @param data
* @param config
* @returns Method实例
*/
public post<Responded = unknown, Transformed = unknown>( public post<Responded = unknown, Transformed = unknown>(
url: string, url: string,
data?: RequestBody, data?: RequestBody,
config?: AlovaMethodCreateConfig<AlovaGenerics, Responded, Transformed>, config?: AlovaMethodCreateConfig<T, Responded, Transformed>,
): Method<RespondedAlovaGenerics<AlovaGenerics, Responded, Transformed>> { ): Method<RespondedAlovaGenerics<T, Responded, Transformed>> {
return this.instance.Post(url, data, config); return this.instance.Post(url, data, config);
} }
/**
* PUT请求
* @param url
* @param data
* @param config
* @returns Method实例
*/
public put<Responded = unknown, Transformed = unknown>( public put<Responded = unknown, Transformed = unknown>(
url: string, url: string,
data?: RequestBody, data?: RequestBody,
config?: AlovaMethodCreateConfig<AlovaGenerics, Responded, Transformed>, config?: AlovaMethodCreateConfig<T, Responded, Transformed>,
): Method<RespondedAlovaGenerics<AlovaGenerics, Responded, Transformed>> { ): Method<RespondedAlovaGenerics<T, Responded, Transformed>> {
return this.instance.Put(url, data, config); return this.instance.Put(url, data, config);
} }
/**
*
* @param config
* @returns Method实例
*/
public request<Responded = unknown, Transformed = unknown>( public request<Responded = unknown, Transformed = unknown>(
config: AlovaMethodCommonConfig<AlovaGenerics, Responded, Transformed>, config: AlovaMethodCommonConfig<T, Responded, Transformed>,
): Method<RespondedAlovaGenerics<AlovaGenerics, Responded, Transformed>> { ): Method<RespondedAlovaGenerics<T, Responded, Transformed>> {
return this.instance.Request(config); return this.instance.Request(config);
} }
} }
export { AlovaClient }; export { AlovaClient, VueHook };
export * from 'alova'; export * from 'alova';
export * from 'alova/client'; export * from 'alova/client';

25
pnpm-lock.yaml

@ -153,6 +153,9 @@ catalogs:
'@vueuse/motion': '@vueuse/motion':
specifier: ^3.0.3 specifier: ^3.0.3
version: 3.0.3 version: 3.0.3
alova:
specifier: ^3.3.4
version: 3.3.4
ant-design-vue: ant-design-vue:
specifier: ^4.2.6 specifier: ^4.2.6
version: 4.2.6 version: 4.2.6
@ -1705,6 +1708,9 @@ importers:
'@vueuse/motion': '@vueuse/motion':
specifier: 'catalog:' specifier: 'catalog:'
version: 3.0.3(magicast@0.3.5)(vue@3.5.17(typescript@5.8.3)) version: 3.0.3(magicast@0.3.5)(vue@3.5.17(typescript@5.8.3))
alova:
specifier: 'catalog:'
version: 3.3.4
echarts: echarts:
specifier: 'catalog:' specifier: 'catalog:'
version: 5.6.0 version: 5.6.0
@ -2011,6 +2017,9 @@ packages:
resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==} resolution: {integrity: sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==}
engines: {node: '>=10'} engines: {node: '>=10'}
'@alova/shared@1.3.1':
resolution: {integrity: sha512-ijSOaFLUFcVzMKSY3avoEE5C03/p9atjMDPBwvNkwnzaCrhv6/m4A121NdadF8YlHCRuifyYfz90IyEdMXTsJg==}
'@ampproject/remapping@2.3.0': '@ampproject/remapping@2.3.0':
resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==} resolution: {integrity: sha512-30iZtAPgz+LTIYoeivqYo853f02jBYSd5uGnGpkFV0M3xOt9aN73erkgYAmZU43x4VfqcnLxW9Kpg3R5LC4YYw==}
engines: {node: '>=6.0.0'} engines: {node: '>=6.0.0'}
@ -5559,6 +5568,10 @@ packages:
alien-signals@1.0.13: alien-signals@1.0.13:
resolution: {integrity: sha512-OGj9yyTnJEttvzhTUWuscOvtqxq5vrhF7vL9oS0xJ2mK0ItPYP1/y+vCFebfxoEyAz0++1AIwJ5CMr+Fk3nDmg==} resolution: {integrity: sha512-OGj9yyTnJEttvzhTUWuscOvtqxq5vrhF7vL9oS0xJ2mK0ItPYP1/y+vCFebfxoEyAz0++1AIwJ5CMr+Fk3nDmg==}
alova@3.3.4:
resolution: {integrity: sha512-UKKqXdvf8aQ4C7m3brO77YWe5CDz8N59PdAUz7M8gowKUUXTutbk0Vk5DRBrCe0hMUyyNMUhdCZ38llGxCViyQ==}
engines: {node: '>= 18.0.0'}
ansi-align@3.0.1: ansi-align@3.0.1:
resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==}
@ -9883,6 +9896,9 @@ packages:
resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==} resolution: {integrity: sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==}
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
rate-limiter-flexible@5.0.5:
resolution: {integrity: sha512-+/dSQfo+3FYwYygUs/V2BBdwGa9nFtakDwKt4l0bnvNB53TNT++QSFewwHX9qXrZJuMe9j+TUaU21lm5ARgqdQ==}
rc9@2.1.2: rc9@2.1.2:
resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==} resolution: {integrity: sha512-btXCnMmRIBINM2LDZoEmOogIZU7Qe7zn4BpomSKZ/ykbLObuBdvG+mFq11DL6fjH1DRwHhrlgtYWG96bJiC7Cg==}
@ -11857,6 +11873,8 @@ snapshots:
'@alloc/quick-lru@5.2.0': {} '@alloc/quick-lru@5.2.0': {}
'@alova/shared@1.3.1': {}
'@ampproject/remapping@2.3.0': '@ampproject/remapping@2.3.0':
dependencies: dependencies:
'@jridgewell/gen-mapping': 0.3.12 '@jridgewell/gen-mapping': 0.3.12
@ -15912,6 +15930,11 @@ snapshots:
alien-signals@1.0.13: {} alien-signals@1.0.13: {}
alova@3.3.4:
dependencies:
'@alova/shared': 1.3.1
rate-limiter-flexible: 5.0.5
ansi-align@3.0.1: ansi-align@3.0.1:
dependencies: dependencies:
string-width: 4.2.3 string-width: 4.2.3
@ -20549,6 +20572,8 @@ snapshots:
range-parser@1.2.1: {} range-parser@1.2.1: {}
rate-limiter-flexible@5.0.5: {}
rc9@2.1.2: rc9@2.1.2:
dependencies: dependencies:
defu: 6.1.4 defu: 6.1.4

Loading…
Cancel
Save