Фронтенд-инжиниринг丨Vue3丨TS丨Инкапсулирует интерфейсы, которые запрашивают несколько разных доменов

внешний фреймворк
Фронтенд-инжиниринг丨Vue3丨TS丨Инкапсулирует интерфейсы, которые запрашивают несколько разных доменов

что было сказано раньше

В этой статье в основном описываются некоторые бизнес-сценарии, встречающиеся в проекте, и извлекаются решения. Для справки~

В проекте мы можем столкнуться с таким сценарием, интерфейс, запрошенный проектом, такой какhttps://a.com/xxx, из-за пересечения сервисов также может возникнуть необходимость запросить интерфейс второго доменного имени, напримерhttps://b.com/xxx

Для этого сценария мы можем придумать несколько решений:
(Примечание. Из-за политики браузера с тем же источником, после упаковки и выпуска внешнего проекта мы обычно размещаем ресурсы в том же домене, что и служба внутреннего интерфейса. Поэтому, когда есть второй интерфейс домена, появится междоменный запрос. Запрос привел к сбою запроса.)

  1. Бэкэнд обрабатывает запрос «второй интерфейс домена», что эквивалентно действию прокси. Таким образом, у внешнего интерфейса не будет междоменных проблем, и не нужно будет делать никаких других вещей.

**Проблемы:** Если вы просто действуете как агент, лично я чувствую, что возникает ощущение связанности, и метод менее элегантен.

  1. Запросите интерфейс для двух разных доменов на внешнем интерфейсе.

Существует проблема:

  • Из-за политики одинакового происхождения браузера должен быть междоменный интерфейс домена, а серверная часть должна установить белый список, чтобы разрешить междоменный доступ.
  • Вообще говоря, мы будем инкапсулировать кадр запроса, что-то вродеrequest.get('getUser'), мы также установим «baseURL» в качестве имени домена по умолчанию, напримерhttps://a.com. Таким образом, запрос по умолчанию, инициированный «запросом»,https://a.comсоответствующие интерфейсы ниже.
    запросить доменное имяhttps://b.comКак мы должны инкапсулировать соответствующие интерфейсы?

Основываясь на анализе двух вышеупомянутых решений, мы нашли лучшее решение, пожалуйста, продолжайте читать:

Посмотрим на конечный эффект после обработки упаковки.

В демонстрации в этой статье в качестве примера используется интерфейс запроса Nuggets, Sifu и Jianshu.

// ...
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В качестве метода для его свойств как двух разных интерфейсов домена мы можем запросить несколько разных интерфейсов домена во внешнем проекте. Далее давайте взглянем на соответствующий код реализации (в настоящее время показана только часть основного кода)~

Вторичный пакет axiosrequestзапросить плагин

Здесь мы беремaxiosНапример, сначала инкапсулируйте его:

// 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 = '';
  // 开发模式开启代理
  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.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);

Использование режима политики для добавления различных интерфейсов доменатестовая/производственная средадоменное имя.

Паттерн стратегии, расширяющий метод $request.get

// 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',
        // 在 headers 加入 token 之类的凭证
        headers: {
          'my-token': 'jianshu-test'
        }
      });
    }
  },
  post: {
     // ...
  }
};
// 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;

С помощью метода extend класса Request мы можем расширить метод get класса $request, чтобы элегантно вызывать другие интерфейсы домена.

Режим стратегии, в соответствии с «кодом», возвращаемым интерфейсом, будет отображаться глобальное всплывающее сообщение об ошибке.

import validator from './validator';

Учитывая, что ключ и значение параметра «код» разных интерфейсов домена несовместимы, например, код Nuggetserr_no, код шифуstatus, но Цзяньшу не разрабатывал возвращенный код ~

Рассмотрим подробнее два фрагмента кода (в настоящее время показана только часть основного кода):

// src/plugins/request/strategies
import { 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/validator
import 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;

Поскольку интерфейсы разных доменов могут разрабатываться разными бэкэнд-разработчиками, очень распространенной проблемой является непоследовательность стиля вывода.Здесь режим стратегии используется для гибкой настройки. Когда серверная часть возвращает ошибку бизнес-логики, это можно сделатьглобальное сообщение об ошибкеИли **равномерно перейти на страницу входа**. Весь интерфейсный проект обеспечивает лучшую унификацию.

Прокси для прокси нескольких доменов

Агент конфигурации локального узла разработки должен быть основной операцией каждого малого партнера. теперь мыместное развитие, независимо от того, поддерживает ли бэкэнд междоменное подключение, добавьте прокси-сервер к каждому домену.Этот шаг также необходим для достижения единства. В настоящее время нам нужно проксировать три домена:

// 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'),
    // ...
  }
};
// ...

Объявление global.d.ts в среде TS делает вызов более удобным

// src/global.d.ts
import { 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 {};

Уведомление

При официальном запуске проекта, помимо основного интерфейса мастера и интерфейсов разных доменов других веток, на сервере необходимо включить междоменный белый список.

Суммировать

В этой статье представлена ​​идея инкапсуляции внешнего проекта для запроса интерфейсов из нескольких разных доменов.Vue3+TS.
Сложность различных бизнес-сценариев проекта непостоянна, и может потребоваться дополнительная инкапсуляция.Абстрактная архитектура для бизнеса — это архитектура, которая не хулиганит.
Выше описаны только некоторые основные коды, и вам нужно посмотреть исходный код для лучшего понимания.Нажмите на меня, чтобы просмотреть исходный код.