likes
comments
collection
share

Web前端开发、前后端分离开发模式、多环境接口(API)域名配置与解决方案

作者站长头像
站长
· 阅读数 9

写在前面:

        在PC Web、移动H5、APP、小程序等Web项目开发过程中,一般项目都是采用前后端分离的开发方式,而前后端的数据交互也几乎都是以调API(后端接口)来进到相互通信的,也就是用到所谓的 Ajax(Asynchronous Javascript And XML 即异步JavaScript和XML 局部刷新技术, 主要用于Web前端 与 后台服务器之间进行数据传输的。该技术可以在不重新加载或刷新整个网页的情况下,对网页的某部分进行更新(该技术最早用于在线地图)。

就Web前端项目开发而言,常用的数据获取方法有:

  1. XHR(XML Http Request) 可以用于获取任何类型的数据,而不仅仅是 XML。它甚至支持 HTTP 以外的协议(包括 file:// 和 FTP)数据通过回调函数获取。
  2. Fetch API包括跨域请求,Fetch API 是上面XHR(XMLHttpRequest)的升级版,数据通过Promise异步调用获取。
  3. WebSocket API 全双工通信的协议,前后端都可以实时通信(常用于聊天室、数据大屏等),它让浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以实时的进行数据互相传送。

        以上3种数据传输方法是目前比较常用,即便是现在市面上用得最多的$.ajax()、axiso插件等都是在这基础上做的二次封装。

存在问题:

        在开发项目时,一般都Ajax调用后端接口的地方是非常多的,小的项目可能就几到几十个不等,大的项目就可能上百或上千之多,好在现在我们都喜欢模块化开发,一般都会把数据请求方法专门封装成一个公共模块,供整个Web前端项目使用,这样一来,我们就只需要在数据请求方法中配置一次后端接口域名就可以了。

        但是,在实际的项开发过程中,项目一般都会分为开发环境、测试环境、预发布环境、生产环境等等,不同环境的后端接口域名也是不一样的,这里意味着,在上线不同环境的代码时,需要配置对应的后端接口域名。

        所以如果每上一个环境,就改一次后端接口域名的话,也没事,那你慢慢改吧!哈哈,开个玩笑,不要笑哈!

解决方案:

        如今的Web前端开发在做项目时,为了出活快、提高开发效率,一般都会采用自动、化工程化、脚手架等构建工具来辅助开发。

常见的构建工具:GulpGruntRollupFISWebpackVite

        有了辅助工具,我们就可以轻松的为各个环境配置好对应的配置文件。在调试或者打包的时通过自动化方式自动适配置不同的环境。 

方案一、配置环境变量文件 .env 

1、什么是.env文件:

****简单来讲 .env文件就是在运行项目时的环境配置文件,可在该文件里面设置相应的环境变量,以实现差异化配置。

2、.env文件配置命名说明:

  • .env:全局默认配置文件,适应所有环境(就是无论下面如个环境都会加载合并这个文件)。
  • .env.development:开发环境配置文件
  • .env.test:测试环境配置文件
  • .env.production:生产环境配置文件

注: 以上几个文件的命名为固定格式,不可随意改变,否则将读取不到对应的文件。

3、.env配置文件内容设置:

语法格式:常量名=常量值

.env配置文件中的内容常量可以根据自己的需要定义,一般都常量名都用全大写。

# 端口号
PORT=3000

# 资源公共路径
PUBLIC_PATH=/

# 网站标题
APP_TITLE=我的应用

# 后端API域名
API_URL=https//:www.xxx.com/index.php

# 是否开启mock数据
USE_MOCK=true

注意:

        1、在.env配置完成后,需要重启服务才能生效!

        2、在不同的脚手架构建工具中,配置常量时,可能需要在常量名的前面加上前缀,然后在process.env中才能获取得到哦!

如:在Vue cli 中配置时,需要在常量名的前面加上 VUE_APP_

# 端口号
VUE_APP_PORT=3000

# 资源公共路径
VUE_APP_PUBLIC_PATH=/

# 网站标题
VUE_APP_APP_TITLE=我的应用

# 后端API域名
VUE_APP_API_URL=https//:www.xxx.com/index.php

# 是否开启mock数据
VUE_APP_USE_MOCK=true

如:在Vite 中配置时,需要在常量名的前面加上 VITE_

# 端口号
VITE_PORT=3000

# 资源公共路径
VITE_PUBLIC_PATH=/

# 网站标题
VITE_APP_TITLE=我的应用

# 后端API域名
VITE_API_URL=https//:www.xxx.com/index.php

# 是否开启mock数据
VITE_USE_MOCK=true

4、.env配置文件内容读取:

  • 在package.json文件中使用:

****在.env配置文件中定义好内容后,如在Vue Cli中会自动配置好相关的映射匹配,也可以自己手动配置,在构建不同环境的代码时通过 --mode指定对应的.env配置文件。

        如:使用Vue Cli构建工具时,在项目根目录中的package.json文件里的script配置项中,添加如下代码:

"scripts": {
    "serve": "vue-cli-service serve",
    
    "build:dev": "vue-cli-service build --mode development",
    "build:test": "vue-cli-service build --mode test",
    "build": "vue-cli-service build --mode production",

    "lint": "vue-cli-service lint"
},

        这一样来,我们可以根据不同的环境,调用对应的打包命令,构建出对应环境的代码啦!

        当然,如果代码管理做了类似 Jenkins 持续集成工具,来做代码自动代布的话,根据对应的Git仓库(以Gitlab为例)在各个代码分支(dev、test、master)都对应的部署好Jenkins工具,这样就可以直接在Jenkins中配置打包并发布代码,前端开发人员连打包操作都不用做了,每次新增或修改完代码后,直接push到对应分支就OK啦。

  • 在具体的 .vue 、.js 、.ts 、.jsx 、.tsx等文件中使用:

        在项目实际开发时,我们可以通过process.env 或 import.meat 来调用对应.env配置文件中的内容。

如:在Vue Cli中时用:process.env.VITE_API_URL  // 就可以得以后端API域名

<script setup lang="ts">
  // This starter template is using Vue 3 <script setup> SFCs
  // Check out https://vuejs.org/api/sfc-script-setup.html#script-setup
  
  // 通过process.env 就可以获取环境变量数据了

  console.log('env配置文件内容:', process.env);
  console.log('env.xxx配置文件中的后端API域名:', process.env.VITE_API_URL);
  
</script>

<template>
    <div class="page"></div>
</template>

<style scoped lang="less">
    .page {
        width: 100vw; height: 100vh;
    }
</style>

如:在Vite中时用:import.meta.env.VITE_API_URL  // 就可以得以后端API域名

<script setup lang="ts">
  // This starter template is using Vue 3 <script setup> SFCs
  // Check out https://vuejs.org/api/sfc-script-setup.html#script-setup
  
  console.log('meta配置文件内容:', import.meta);
  console.log('env配置文件内容:', import.meta.env);
  console.log('env.xxx配置文件中的后端API域名', import.meta.env.VITE_API_URL);
  
</script>

<template>
    <div class="page"></div>
</template>

<style scoped lang="less">
    .page {
        width: 100vw; height: 100vh;
    }
</style>

方案二、自定义配置环境文件 env.config.js [ 强烈推荐!]

​ 该方案是完全由自己定义,不通过.env配置文件来进行配置,且适用于所有环境。

思路设计:

        首先,如果前端项目 和 后端项目都是同一个域名,那就再好不过了,直接用 "/" (当前域名)来代替所有环境的API域名。

        其次,如果前端项目 和 后端项目不是同一个域名,并且每套(开发、测试、生产)环境的前后端域名都不一样时,以下方案就大有派场啦!

        简讲,就是自己定义一个用于全局公共的env.config.js文件,在需要的页面中引入加载进去就OK了!

        原理:就是用一个全局常量或变量 对象(建议用常量,因为常量是一经建立就不要随意修改,即便是要改,也是一改全改)来存储我们想要的数据,主要核心是:根据判断前端代码当前所在的环境域名 “location.hostname” 来获取对应的环境配置内容。

        具体文件配置内容如下!

env.config.js文件配置:

// 环境变量对象
const config = {
    env: 'localhost',                           // 默认为本地环境 localhost

    // 本地环境
    localhost: {
        __URL__: `/api`,                        // 后端API域名(注:/api 是在本地开发时,配置的本地代理服务接口)
        __NET__: location.origin,               // 前端域名
        __WSS__: `//www.backend-dev-wss.com`,   // 后端SocketAPI域名
        __ENV__: 'localhost',                   // 环境变量
        __API__VERSION: `/api/v1`,              // API版本号
        __UPLOAD__IMAGE: `/upload/image`,       // 图片上传路径

        // ... 可以根据自己的需要去添加,注 在其他环境中也要对应的加哦!!
    },

    // 开发环境
    development: {
        __URL__: `//www.backend-dev.com`,
        __NET__: location.origin,
        __WSS__: `wss://www.backend-dev-wss.com`,
        __ENV__: 'development',
        __API__VERSION: `/api/v1`,
        __UPLOAD__IMAGE: `/upload/image`,
    },

    // 测试环境
    test: {
        __URL__: `//www.backend-test.com`,
        __NET__: location.origin,
        __WSS__: `wss://www.backend-test-wss.com`,
        __ENV__: 'test',
        __API__VERSION: `/api/v1`,
        __UPLOAD__IMAGE: `/upload/image`,
    },

    // 生产环境
    production: {
        __URL__: `//www.backend.com`,
        __NET__: location.origin,
        __WSS__: `wss://www.backend-wss.com`,
        __ENV__: 'production',
        __API__VERSION: `/api/v1`,
        __UPLOAD__IMAGE: `/upload/image`,
    }
};

// 根据当代码所在运行环境的域名判断
switch (location.hostname || document.domain) {
    // 开发环境前端域名
    case `www.dev.com`:
        config.env = `development`;
        break;

    // 测试环境前端域名
    case `www.test.com`:
        config.env = `test`;
        break;

    // 生产环境前端域名
    case `www.pord.com`:
        config.env = `production`;
        break;
};

// 向外暴露判断后所对应的环境变量内容
export default config[config.env];

        此时在这个全局公共的env.config.js文件中,它会自动通过判断各个环境的当前域名来获取对应的配置内容数据!

        这样一来,我们就不用每次在上线不同环境代码的时候,通过不同的构建命令(如 yarn build:dev、yarn build:test、yarn build ......等打包命令)来打包对应环境的代码了。

env.config.js文件引用:

<script setup lang="ts">
  // This starter template is using Vue 3 <script setup> SFCs
  // Check out https://vuejs.org/api/sfc-script-setup.html#script-setup
  
  // 引入公共配置文件env.config.js
  import env from "../config/env.config.js";

  console.log('配置文件内容:', env);
  console.log('配置文件中的后端API域名', env.__URL__);
  
</script>

例实代码:

        比如在自定义数据请求封装模块 request.js 中使用 env.config.js环境配置文件,这里以二次封装axios为例。

import type { AxiosRequestConfig, AxiosResponse, AxiosError, AxiosPromise } from "axios";
import axios from "axios";
import qs from "qs";
import { Toast } from "vant";
import { ContentTypeEnum } from "@/enums/httpEnum";

// 在这里引入公共配置文件env.config.js
import env from "../config/env.config.js";

const http = axios.create({
    baseURL: env.__URL__,    // 在这里使用后端API域名常量 __URL___
    timeout: 60 * 1000,
    withCredentials: true,
});

http.interceptors.request.use(
    (cfg: AxiosRequestConfig) => {
        if (
            cfg.headers &&
            cfg.headers?.["Content-Type"] === ContentTypeEnum.FORM_URLENCODED
        ) {
            cfg.data = qs.stringify(cfg.data, {
                arrayFormat: "brackets",
            });
        }
        return cfg;
    },
    (err: AxiosError) => {
        return Promise.reject(err);
    }
);

http.interceptors.response.use(
    (res: AxiosResponse<any>) => {
        if (res.status == 200) {
            if (0 > res.data?.code) {
                Toast.fail({
                    icon: "warning-o",
                    message: res.data?.msg,
                });
            }
            return Promise.resolve(res.data);
        } else {
            return Promise.reject(res);
        }
    },
    (err: AxiosError) => {
        Toast.fail({
            icon: "warning-o",
            message: "网络异常,请稍后再试!",
        });
        return Promise.reject(err);
    }
);

export default http;