What I said earlier

This article focuses on some of the business scenarios encountered in the project and the solutions extracted. For your reference

In a project, we might have a scenario where the project requests an interface such as https://a.com/xxx and, due to the intersection of services, a second domain name such as https://b.com/xxx

For this scenario, we might come up with several solutions: (Note: because of the browser same-origin policy, after a front-end project is packaged and published, we usually put resources in the same domain as the back-end interface services. So when there is a second domain interface, there is a cross-domain request that causes the request to fail.

  1. The back-end processes the request “second domain interface,” which is equivalent to a proxy action. That way the front end doesn’t have cross-domain problems and you don’t have to do anything else.

** has problems: if ** is simply acting as an agent, I feel there is a feeling of coupling, and the method is not elegant.

  1. Request interfaces from two different domains at the front end.

Existing problems:

  • Due to the same origin policy of the browser, one domain interface must cross domains, and the backend must be set to allow cross-domain whitelist.
  • Generally we wrap the request framework, like thatrequest.get('getUser'), we will also set a “baseURL” as the default domain name, such ashttps://a.com. In this case, “Request” is the default requesthttps://a.comRelated interfaces under.

    That request domain namehttps://b.comHow do we encapsulate related interfaces?

According to the analysis of the above two schemes, we have obtained a better treatment scheme, please continue to read:

Let’s take a look at the final result of processing encapsulation

This article demo to request nuggets, think not, Jane book interface as an example.

// ...
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');
};
// ...
Copy the code

We encapsulate $Request as the primary object and extend the.get methods, Sifou, jianshu properties as two different domain interface methods, thus enabling us to request multiple different domain interfaces in a single front-end project. Let’s take a look at the implementation code (only some of the core code is shown for now) ~

Secondary encapsulation of axiosrequestRequest the plugin

Here we take Axios as an example and wrap it:

// src/plugins/request
import 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 = ' ';
  // Enable the agent in development mode
  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) {
        // A business error started verifying that the request was successfulvalidator.start(config.branch! , data, config);return data;
      }
      return Promise.reject(new Error('Response Error'));
    })
    .catch(error= > {
      // Network related error, here can be used to pop up the global prompt
      return Promise.reject(error);
    });
};

/ * * *@desc Request method class encapsulation */
class Request {
  private extends: any;
  // To be treated as a plug-in, the install method is required
  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;
Copy the code

Now let’s explain the “Request” plug-in

Policy mode, interface domain name configuration for different environments

import apiConfig from '@/api.config';

// @/api.config
const 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);
Copy the code

Add test/formal environment domain names for different domain interfaces using policy mode.

Policy mode, extending the $request.get method

// src/plugins/request/branchs/jianshu
import { _request } from '.. /request';
export default {
  get: {
    jianshu(path: string, data: object = {}, config: IAxiosRequestConfig = {}) {
      return_request({ ... config,method: 'GET'.url: path,
        data,
        branch: 'jianshu'.// Add a token to headers
        headers: {
          'my-token': 'jianshu-test'}}); }},post: {
     // ...}};Copy the code
// src/plugins/request
import { 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;
Copy the code

By extending the Request class’s extend method, we can extend the $Request get method to implement elegant calls to other domain interfaces.

In policy mode, a global error message is displayed based on the code returned by the interface

import validator from './validator';

Considering that the key and value of the output parameter “code” of different domain interfaces are inconsistent, for example, the code of digging gold is err_NO and the code of thinking no is status. However, Jane does not design the returned code ~

Let’s take a closer look at two pieces of code (only part of the core code is shown for now) :

// src/plugins/request/strategies
import { parseCode, showMsg } from './helper';
import router from '@/router';
import { IStrategieInParams, IStrategieType } from './index.type';
/ * * *@desc Business logic related error handling policy */ for successful request returns
const strategies: Record<
  IStrategieType,
  (obj: IStrategieInParams) = > string | undefined
> = {
  // Service logic exception
  BUSINESS_ERROR({ data, codeKey, codeValue }) {
    const message = 'System exception, please try again later';
    data[codeKey] = parseCode(data[codeKey]);
    if (data[codeKey] === codeValue) {
      showMsg(message);
      returnmessage; }},// No login is authorized
  NOT_AUTH({ data, codeKey, codeValue }) {
    const message = 'User not logged in, please log in first';
    data[codeKey] = parseCode(data[codeKey]);
    if (data[codeKey] === codeValue) {
      showMsg(message);
      router.replace({ path: '/login' });
      returnmessage; }}/ *... More strategies... * /
};
export default strategies;
Copy the code
// src/plugins/request/validator
import Validator from './validator';
const validator = new Validator();
validator
  .add('master'[{strategy: 'BUSINESS_ERROR'.codeKey: 'err_no'./* If code is incorrect, the value is 1. If 1 is returned, the global popup will be displayed. To see the effect, you can change it to 0 and only test to show the global error popup, */
      codeValue: 1
    },
    {
      strategy: 'NOT_AUTH'.codeKey: 'err_no'./* If the code is incorrect, the value is 3000. If 3000 is returned, the login page is automatically redirected. To see the effect, you can change it to 0 and only test the jump to the login page */
      codeValue: 3000
    }
  ])
  .add('sifou'[{strategy: 'BUSINESS_ERROR'.codeKey: 'status'.// Set the value to 1 if code is incorrect
      codeValue: 1
    },
    {
      strategy: 'NOT_AUTH'.codeKey: 'status'.codeValue: 3000}]);/ *... More domain related configurations... * /
// .add();
export default validator;
Copy the code

Because different domains of interfaces may be developed by different back-end developers, inconsistent input parameter styles are a common problem, and policy patterns are used here for a flexible configuration. When a service logic error is returned from the backend, a global error message is displayed or the login page is redirected to **. The whole front-end engineering is better unified.

Proxy Proxies multiple domains

The local development node configuration agent should be a basic operation for everyone. We now add proxies to each domain in local development, regardless of whether cross-domain is enabled at the back end. This step is also to achieve uniformity. Currently we need to delegate three domains:

// vue.config.js
// ...
const proxy = {
  '/master': {
    target: apiConfig(MY_ENV, 'master'),
    secure: true.changeOrigin: true.// The path of the proxy is master, because this way, can be targeted to the proxy, not other useless proxy. But the actual requested interface does not need the master, so remove it before requesting it
    pathRewrite: {
      '^/master': ' '}},'/jianshu': {
    target: apiConfig(MY_ENV, 'jianshu'),
    // ...
  },
  '/sifou': {
    target: apiConfig(MY_ENV, 'sifou'),
    // ...}};// ...
Copy the code

TS environment global.d. TS declaration, so that call more convenient

// src/global.d.ts
import { ComponentInternalInstance } from 'vue';
import { AxiosRequestConfig } from 'axios';
declare global {
  interface IAxiosRequestConfig extends AxiosRequestConfig {
    // What is the domain name of the interface that is currently requestedbranch? : string;// Display loading globally. Default is falseloading? : boolean;/ *... More configuration... * /
  }

  type IRequestMethod = (path: string, data? : object, config? : IAxiosRequestConfig) = > any;
  type IRequestMember = IRequestMethod & {
    jianshu: IRequestMethod; } and {sifou: IRequestMethod;
  };
  interface IRequest {
    get: IRequestMember;
    post: IRequestMember;
  }

  interface IGlobalAPI {
    $request: IRequest;

    / *... More global methods... * /
  }

  // Global method hook declaration
  interface ICurrentInstance extends ComponentInternalInstance {
    appContext: {
      config: { globalProperties: IGlobalAPI }; }; }}/** * If you want to write the Vue2 Options Api in the Vue3 framework, you need to add this statement **@example
 * created(){
 *  this.$request.get();
 *  this.$request.get.sifou();
 *  this.$request.get.jianshu();
 * }
 */
declare module '@vue/runtime-core' {
  export interface ComponentCustomProperties {
    $request: IRequest; }}export {};
Copy the code

Pay attention to

When the project is officially launched, the server must enable the cross-domain whitelist for interfaces of different domains except the master interface.

conclusion

This paper provides the idea of encapsulation for a front-end project to request multiple interfaces of different domains. The basic framework is Vue3+TS. Different project business scenarios have varying levels of complexity and may require more encapsulation, but business-specific abstract architectures are the no-hooligan architectures. The above just elaborated some core code, specific or to see the source code to understand more, point I view the source code.