存在问题: 如果只是单纯的做代理,个人觉得有一种耦合的感觉,方法较为不优雅。
存在问题:
request.get('getUser'),我们还会设置一个 “baseURL” 为默认域名,如 https://a.com。这样子 “request” 默认发起的请求都是 https://a.com 下的相关接口。undefined那请求域名 https://b.com 相关接口我们该怎样进行封装呢?针对以上的两个方案分析,我们得出了一个较优的处理方案,请继续往下看:
本文 demo 以请求 掘金,思否,简书 的接口来为例。
// ...const requestMaster = async () => { const { err_no, data, err_msg } = await $request.get('user_api/v1/author/recommend');};const requestSifou = async () => { const { status, data } = await $request.get.sifou('api/live/recommend');};const requestJianshu = async () => { const { users } = await $request.get.jianshu('users/recommended');};// ...我们封装 $request 作为主要对象,并扩展 .get 方法,sifou,jianshu
为其属性作为两个不同域接口的方法,从而实现了我们在一个前端工程中请求多个不同域接口。接下来让我们看看实现的相关代码吧(当前只展示部分核心代码)~
request 请求插件这里我们拿 axios 为例,先对它进行一个封装:
// src/plugins/requestimport axios from 'axios';import apiConfig from '@/api.config';import _merge from 'lodash/merge';import validator from './validator';import { App } from 'vue';export const _request = (config: IAxiosRequestConfig) => { config.branch = config.branch || 'master'; let baseURL = ''; // 开发模式开启代理 if (process.env.NODE_ENV === 'development') { config.url = `/${config.branch}/${config.url}`; } else { baseURL = apiConfig(process.env.MY_ENV, config.branch); } return axios .request( _merge( { timeout: 20000, headers: { 'Content-Type': 'application/json', token: 'xxx' } }, { baseURL }, config ) ) .then(res => { const data = res.data; if (data && res.status === 200) { // 开始验证请求成功的业务错误 validator.start(config.branch!, data, config); return data; } return Promise.reject(new Error('Response Error')); }) .catch(error => { // 网络相关的错误,这里可用弹框进行全局提示 return Promise.reject(error); });};/** * @desc 请求方法类封装
*/
class Request {
private extends: any;
// request 要被作为一个插件,需要有 install 方法
public install: (app: App, ...options: any[]) => any;
constructor() {
this.extends = [];
this.install = () => {};
}
extend(extend: any) {
this.extends.push(extend);
return this;
}
merge() {
const obj = this.extends.reduce((prev: any, curr: any) => {
return _merge(prev, curr);
}, {});
Object.keys(obj).forEach(key => {
Object.assign((this as any)[key], obj[key]);
});
}
get(path: string, data: object = {}, config: IAxiosRequestConfig = {}) {
return _request({
...config,
method: 'GET',
url: path,
params: data
});
}
post(path: string, data: object = {}, config: IAxiosRequestConfig = {}) {
return _request({
...config,
method: 'POST',
url: path,
data
});
}
}
export default Request;import apiConfig from '@/api.config';
// @/api.configconst APIConfig = require('./apiConfig');const apiConfig = new APIConfig();apiConfig .add('master', { test: 'https://api.juejin.cn', prod: 'https://prod.api.juejin.cn' }) .add('jianshu', { test: 'https://www.jianshu.com', prod: 'https://www.prod.jianshu.com' }) .add('sifou', { test: 'https://segmentfault.com', prod: 'https://prod.segmentfault.com' });module.exports = (myenv, branch) => apiConfig.get(myenv, branch);使用策略模式添加不同域接口的 测试/正式环境 域名。
// src/plugins/request/branchs/jianshuimport { _request } from '../request';export default { get: { jianshu(path: string, data: object = {}, config: IAxiosRequestConfig = {}) { return _request({ ...config, method: 'GET', url: path, data, branch: 'jianshu', // 在 headers 加入 token 之类的凭证 headers: { 'my-token': 'jianshu-test' } }); } }, post: { // ... }};// src/plugins/requestimport { App } from 'vue';import Request from './request';import sifou from './branchs/sifou';import jianshu from './branchs/jianshu';const request = new Request();request.extend(sifou).extend(jianshu);request.merge();request.install = (app: App, ...options: any[]) => { app.config.globalProperties.$request = request;};export default request;通过 Request 类的 extend 方法,我们就可以进行扩展 $request 的 get 方法,实现优雅的调用其他域接口。
import validator from './validator';
考虑到不同域接口的出参 “code” 的 key 和 value 都不一致,如掘金的 code 为 err_no,思否的 code 为
status,但是简书却没有设计返回的 code ~
让我们仔细看两段代码(当前只展示部分核心代码):
// src/plugins/request/strategiesimport { parseCode, showMsg } from './helper';import router from '@/router';import { IStrategieInParams, IStrategieType } from './index.type';/** * @desc 请求成功返回的业务逻辑相关错误处理策略
*/
const strategies: Record<
IStrategieType,
(obj: IStrategieInParams) => string | undefined
> = { // 业务逻辑异常 BUSINESS_ERROR({ data, codeKey, codeValue }) { const message = '系统异常,请稍后再试'; data[codeKey] = parseCode(data[codeKey]); if (data[codeKey] === codeValue) { showMsg(message); return message; } }, // 没有授权登录 NOT_AUTH({ data, codeKey, codeValue }) { const message = '用户未登录,请先登录'; data[codeKey] = parseCode(data[codeKey]); if (data[codeKey] === codeValue) { showMsg(message); router.replace({ path: '/login' }); return message; } } /* ...更多策略... */};export default strategies;// src/plugins/request/validatorimport Validator from './validator';const validator = new Validator();validator .add('master', [ { strategy: 'BUSINESS_ERROR', codeKey: 'err_no', /* 配置 code 错误时值为1,如果返回 1 就会全局弹框显示。 想要看到效果的话,可以改为 0,仅测试显示全局错误弹框, */ codeValue: 1 }, { strategy: 'NOT_AUTH', codeKey: 'err_no', /* 配置 code 错误时值为3000,如果返回 3000 就会自动跳转至登录页。 想要看到效果的话,可以改为 0,仅测试跳转至登录页 */ codeValue: 3000 } ]) .add('sifou', [ { strategy: 'BUSINESS_ERROR', codeKey: 'status', // 配置 code 错误时值为1 codeValue: 1 }, { strategy: 'NOT_AUTH', codeKey: 'status', codeValue: 3000 } ]);/* ...更多域相关配置... */// .add();export default validator;因为不同域的接口,可能是不同的后端开发人员开发,所以出参风格不一致是一个很常见的问题,这里采用了策略模式来进行一个灵活的配置。在后端返回业务逻辑错误时,就可以进行
全局性的错误提示 或 统一跳转至登录页 。整个前端工程达成更好的统一化。
本地开发 node 配置代理应该是每个小伙伴的基本操作吧。现在我们在 本地开发
时,不管后端是否开启跨域,都给每个域加上代理,这步也是为了达成一个统一。目前我们需要代理三个域:
// vue.config.js// ...const proxy = { '/master': { target: apiConfig(MY_ENV, 'master'), secure: true, changeOrigin: true, // 代理的时候路径是有 master 的,因为这样子就可以针对代理,不会代理到其他无用的。但实际请求的接口是不需要 master 的,所以在请求前要把它去掉 pathRewrite: { '^/master': '' } }, '/jianshu': { target: apiConfig(MY_ENV, 'jianshu'), // ... }, '/sifou': { target: apiConfig(MY_ENV, 'sifou'), // ... }};// ...// src/global.d.tsimport { ComponentInternalInstance } from 'vue';import { AxiosRequestConfig } from 'axios';declare global { interface IAxiosRequestConfig extends AxiosRequestConfig { // 标记当前请求的接口域名是什么,默认master,不需要手动控制 branch?: string; // 全局显示 loading,默认false loading?: boolean; /* ...更多配置... */ } type IRequestMethod = ( path: string, data?: object, config?: IAxiosRequestConfig ) => any; type IRequestMember = IRequestMethod & { jianshu: IRequestMethod; } & { sifou: IRequestMethod; }; interface IRequest { get: IRequestMember; post: IRequestMember; } interface IGlobalAPI { $request: IRequest; /* ...更多其他全局方法... */ } // 全局方法钩子声明 interface ICurrentInstance extends ComponentInternalInstance { appContext: { config: { globalProperties: IGlobalAPI }; }; }}/** * 如果你在 Vue3 框架中还留恋 Vue2 Options Api 的写法,需要再新增这段声明
*
* @example
* created(){
* this.$request.get();
* this.$request.get.sifou();
* this.$request.get.jianshu();
* }
*/
declare module '@vue/runtime-core' {
export interface ComponentCustomProperties {
$request: IRequest;
}
}
export {};项目正式上线时,除了 master 主要接口,其他分支的不同域接口,服务端需要开启跨域白名单。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。