<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          前端工程丨Vue3丨TS丨封裝請求多個(gè)不同域的接口

          共 15500字,需瀏覽 31分鐘

           ·

          2021-03-17 03:23

          (關(guān)注前端精,讓你前端越來越精 ~ )

          前面說的話

          本文主要講述在項(xiàng)目中遇到的一些業(yè)務(wù)場景,并提煉出來的解決方案。供小伙伴們參考~

          在一個(gè)項(xiàng)目中,我們可能會(huì)遇到這樣子的場景,項(xiàng)目請求的接口如 https://a.com/xxx,由于業(yè)務(wù)的交集,可能還需要請求第二個(gè)域名的接口,如 https://b.com/xxx

          針對這種場景,我們可能會(huì)想到幾個(gè)方案:
          (注意:由于瀏覽器同源策略,一個(gè)前端工程在打包發(fā)布之后,通常我們會(huì)把資源放在與后端接口服務(wù)同一個(gè)域下。所以當(dāng)有第二個(gè)域接口時(shí),就會(huì)出現(xiàn)跨域請求導(dǎo)致請求失敗。)

          1. 后端處理請求 “第二個(gè)域接口”,相當(dāng)于代理動(dòng)作。這樣子前端就不會(huì)有跨域問題,無需做其他事。

          存在問題:如果只是單純的做代理,個(gè)人覺得有一種耦合的感覺,方法較為不優(yōu)雅。

          1. 在前端請求兩個(gè)不同域的接口。

          存在問題:

          • 由于瀏覽器同源策略,必須會(huì)有一個(gè)域的接口跨域,后端需要設(shè)置允許跨域白名單。
          • 一般來說我們會(huì)對請求框架進(jìn)行封裝,類似 request.get('getUser'),我們還會(huì)設(shè)置一個(gè) “baseURL” 為默認(rèn)域名,如 https://a.com。這樣子 “request” 默認(rèn)發(fā)起的請求都是 https://a.com 下的相關(guān)接口。
            那請求域名 https://b.com 相關(guān)接口我們該怎樣進(jìn)行封裝呢?

          針對以上的兩個(gè)方案分析,我們得出了一個(gè)較優(yōu)的處理方案,請繼續(xù)往下看:

          先看下處理封裝后的最終效果

          本文 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 作為主要對象,并擴(kuò)展 .get 方法,sifoujianshu 為其屬性作為兩個(gè)不同域接口的方法,從而實(shí)現(xiàn)了我們在一個(gè)前端工程中請求多個(gè)不同域接口。接下來讓我們看看實(shí)現(xiàn)的相關(guān)代碼吧(當(dāng)前只展示部分核心代碼)~

          二次封裝 axios 的 request 請求插件

          這里我們拿 axios 為例,先對它進(jìn)行一個(gè)封裝:

          // 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 = '';
            // 開發(fā)模式開啟代理
            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(
                  {
                    timeout20000,
                    headers: {
                      'Content-Type''application/json',
                      token'xxx'
                    }
                  },
                  { baseURL },
                  config
                )
              )
              .then(res => {
                const data = res.data;
                if (data && res.status === 200) {
                  // 開始驗(yàn)證請求成功的業(yè)務(wù)錯(cuò)誤
                  validator.start(config.branch!, data, config);
                  return data;
                }
                return Promise.reject(new Error('Response Error'));
              })
              .catch(error => {
                // 網(wǎng)絡(luò)相關(guān)的錯(cuò)誤,這里可用彈框進(jìn)行全局提示
                return Promise.reject(error);
              });
          };

          /**
           * @desc 請求方法類封裝
           */

          class Request {
            private extends: any;
            // request 要被作為一個(gè)插件,需要有 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;

          現(xiàn)在我們來一一解釋 “request” 插件

          策略模式,不同環(huán)境的接口域名配置

          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);

          使用策略模式添加不同域接口的 測試/正式環(huán)境 域名。

          策略模式,擴(kuò)展 $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;

          通過 Request 類的 extend 方法,我們就可以進(jìn)行擴(kuò)展 $request 的 get 方法,實(shí)現(xiàn)優(yōu)雅的調(diào)用其他域接口。

          策略模式,根據(jù)接口返回的 “code” 進(jìn)行全局彈框錯(cuò)誤提示

          import validator from './validator';

          考慮到不同域接口的出參 “code” 的 key 和 value 都不一致,如掘金的 code 為 err_no,思否的 code 為 status,但是簡書卻沒有設(shè)計(jì)返回的 code ~

          讓我們仔細(xì)看兩段代碼(當(dāng)前只展示部分核心代碼):

          // src/plugins/request/strategies
          import { parseCode, showMsg } from './helper';
          import router from '@/router';
          import { IStrategieInParams, IStrategieType } from './index.type';
          /**
           * @desc 請求成功返回的業(yè)務(wù)邏輯相關(guān)錯(cuò)誤處理策略
           */

          const strategies: Record<
            IStrategieType,
            (obj: IStrategieInParams) => string | undefined
          > = {
            // 業(yè)務(wù)邏輯異常
            BUSINESS_ERROR({ data, codeKey, codeValue }) {
              const message = '系統(tǒng)異常,請稍后再試';
              data[codeKey] = parseCode(data[codeKey]);
              if (data[codeKey] === codeValue) {
                showMsg(message);
                return message;
              }
            },
            // 沒有授權(quán)登錄
            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 錯(cuò)誤時(shí)值為1,如果返回 1 就會(huì)全局彈框顯示。
                  想要看到效果的話,可以改為 0,僅測試顯示全局錯(cuò)誤彈框,
                 */

                codeValue1
              },
              {
                strategy'NOT_AUTH',
                codeKey'err_no',
                /* 
                  配置 code 錯(cuò)誤時(shí)值為3000,如果返回 3000 就會(huì)自動(dòng)跳轉(zhuǎn)至登錄頁。
                  想要看到效果的話,可以改為 0,僅測試跳轉(zhuǎn)至登錄頁
                 */

                codeValue3000
              }
            ])
            .add('sifou', [
              {
                strategy'BUSINESS_ERROR',
                codeKey'status',
                // 配置 code 錯(cuò)誤時(shí)值為1
                codeValue1
              },
              {
                strategy'NOT_AUTH',
                codeKey'status',
                codeValue3000
              }
            ]);
          /* ...更多域相關(guān)配置... */
          // .add();
          export default validator;

          因?yàn)椴煌虻慕涌?,可能是不同的后端開發(fā)人員開發(fā),所以出參風(fēng)格不一致是一個(gè)很常見的問題,這里采用了策略模式來進(jìn)行一個(gè)靈活的配置。在后端返回業(yè)務(wù)邏輯錯(cuò)誤時(shí),就可以進(jìn)行 全局性的錯(cuò)誤提示統(tǒng)一跳轉(zhuǎn)至登錄頁。整個(gè)前端工程達(dá)成更好的統(tǒng)一化。

          Proxy 代理多個(gè)域

          本地開發(fā) node 配置代理應(yīng)該是每個(gè)小伙伴的基本操作吧。現(xiàn)在我們在本地開發(fā)時(shí),不管后端是否開啟跨域,都給每個(gè)域加上代理,這步也是為了達(dá)成一個(gè)統(tǒng)一。目前我們需要代理三個(gè)域:

          // vue.config.js
          // ...
          const proxy = {
            '/master': {
              target: apiConfig(MY_ENV, 'master'),
              securetrue,
              changeOrigintrue,
              // 代理的時(shí)候路徑是有 master 的,因?yàn)檫@樣子就可以針對代理,不會(huì)代理到其他無用的。但實(shí)際請求的接口是不需要 master 的,所以在請求前要把它去掉
              pathRewrite: {
                '^/master'''
              }
            },
            '/jianshu': {
              target: apiConfig(MY_ENV, 'jianshu'),
              // ...
            },
            '/sifou': {
              target: apiConfig(MY_ENV, 'sifou'),
              // ...
            }
          };
          // ...

          TS 環(huán)境下 global.d.ts 聲明,讓調(diào)用更方便

          // src/global.d.ts
          import { ComponentInternalInstance } from 'vue';
          import { AxiosRequestConfig } from 'axios';
          declare global {
            interface IAxiosRequestConfig extends AxiosRequestConfig {
              // 標(biāo)記當(dāng)前請求的接口域名是什么,默認(rèn)master,不需要手動(dòng)控制
              branch?: string;
              // 全局顯示 loading,默認(rèn)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 {};

          注意

          項(xiàng)目正式上線時(shí),除了 master 主要接口,其他分支的不同域接口,服務(wù)端需要開啟跨域白名單。

          總結(jié)

          本文為一個(gè)前端項(xiàng)目請求多個(gè)不同域的接口,提供了封裝的思路,基礎(chǔ)框架為 Vue3+TS。
          不同的項(xiàng)目業(yè)務(wù)場景復(fù)雜程度不一致,可能還需要更多的封裝,針對業(yè)務(wù)的抽象架構(gòu)才是不耍流氓的架構(gòu)。
          以上只是闡述了一些核心代碼,具體還是要看源碼才能更加了解。

          感謝您的點(diǎn)贊和在看 ??  

          點(diǎn)擊【閱讀原文】,查看源碼

          瀏覽 55
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧美色鬼| 国产成人精品 视频 | 爱草逼爱草逼爱草逼爱草逼爱草逼爱草逼 | 五月综合视频 | 操b视频无码 |