Web前端开发、前后端分离开发模式、多环境接口(API)域名配置与解决方案
写在前面:
在PC Web、移动H5、APP、小程序等Web项目开发过程中,一般项目都是采用前后端分离的开发方式,而前后端的数据交互也几乎都是以调API(后端接口)来进到相互通信的,也就是用到所谓的 Ajax(Asynchronous Javascript And XML 即异步JavaScript和XML 局部刷新技术, 主要用于Web前端 与 后台服务器之间进行数据传输的。该技术可以在不重新加载或刷新整个网页的情况下,对网页的某部分进行更新(该技术最早用于在线地图)。
就Web前端项目开发而言,常用的数据获取方法有:
- XHR(XML Http Request) 可以用于获取任何类型的数据,而不仅仅是 XML。它甚至支持 HTTP 以外的协议(包括 file:// 和 FTP)数据通过回调函数获取。
- Fetch API包括跨域请求,Fetch API 是上面XHR(XMLHttpRequest)的升级版,数据通过Promise异步调用获取。
- WebSocket API 全双工通信的协议,前后端都可以实时通信(常用于聊天室、数据大屏等),它让浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以实时的进行数据互相传送。
以上3种数据传输方法是目前比较常用,即便是现在市面上用得最多的$.ajax()、axiso插件等都是在这基础上做的二次封装。
存在问题:
在开发项目时,一般都Ajax调用后端接口的地方是非常多的,小的项目可能就几到几十个不等,大的项目就可能上百或上千之多,好在现在我们都喜欢模块化开发,一般都会把数据请求方法专门封装成一个公共模块,供整个Web前端项目使用,这样一来,我们就只需要在数据请求方法中配置一次后端接口域名就可以了。
但是,在实际的项开发过程中,项目一般都会分为开发环境、测试环境、预发布环境、生产环境等等,不同环境的后端接口域名也是不一样的,这里意味着,在上线不同环境的代码时,需要配置对应的后端接口域名。
所以如果每上一个环境,就改一次后端接口域名的话,也没事,那你慢慢改吧!哈哈,开个玩笑,不要笑哈!
解决方案:
如今的Web前端开发在做项目时,为了出活快、提高开发效率,一般都会采用自动、化工程化、脚手架等构建工具来辅助开发。
常见的构建工具:Gulp、Grunt、Rollup、FIS、Webpack、Vite
有了辅助工具,我们就可以轻松的为各个环境配置好对应的配置文件。在调试或者打包的时通过自动化方式自动适配置不同的环境。
方案一、配置环境变量文件 .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;
转载自:https://juejin.cn/post/7253287098064126008