5.6 Vue/React/Angular 接口代理
5.6.1 历史背景
在现在主流的 Web 项目开发中,越来越多的开发者选择使用 Vue/React/Angular
三大框架进行开发,这三大框架和传统开发模式最大的不同是前者采用前后端分离的方式,而后者统一由后端程序员编写。
在前后端分离的模式中,前后端程序员各司其职,后端程序负责编写接口(API),前端程序员负责编写客户端请求后端接口(API)并进行数据绑定。
但这里暴露出了一个工作效率极低且易出错的问题,那就是前端程序需要将后端几百个甚至上千个接口进行一一对应编写,大多都是采用 $.ajax
或 axios
的方式。
一旦后端接口参数或返回值发生改变,前端程序员需要一一进行勘正,一旦出现纠正不完全就会导致系统无法响应或接收错误的用户消息从而造成不必要的维护工作和成本浪费。
5.6.2 如何解决?
Furion
框架编写的所有后端接口都会生成规范化的 swagger.json
文件,使用该文件可以在 https://editor.swagger.io 生成任何支持标准 swagger
的界面或客户端代码。
自此,前端程序员再也无需自己手写 $.ajax
和 axios
代码,这部分代码全部自动生成,以后开发效率至少提高一半以上。
5.6.3 生成客户端请求代码
TypeScript
和 JavaScript
以下教程仅适用于 Vue/React/Angular
的 TypeScript
类型项目,暂不支持 JavaScript
。
为了项目良好的发展和维护,建议使用 TypeScript
进行编写。
5.3.3.1 生成客户端代码
- 打开规范化文档(Swagger)首页,并点击顶部
/swagger/xxxx/swagger.json
到新窗口打开。
- 接着全选并复制全部内容
- 打开 https://editor.swagger.io 官网并粘贴进去
Furion
也提供了 Swagger-Editor.rar 离线包,可直接下载解压并双击 index.html
即可。
- 最后点击顶部的
Generate Client
选择对应的语言/框架进行生成即可。
5.6.3.2 Vue/React
配置
点击 Generate Client
顶部菜单并选择 typescript-axios
进行下载。
下载成功之后拷贝下图选择的目录和文件:
接着打开你的 Vue
或 React
项目,并在 src
目录下创建 api-services
目录并将刚刚复制的目录文件放在里面。
接下来通过 npm
或 yarn
安装 axios
包
# npm 方式
npm i axios@0.21.4
# yarn 方式
yarn add axios@0.21.4
axios
版本说明注意 axios
版本必须是 0.21.4
版本,如果安装其他版本可能会出现无法编译的情况。
接着下载 Furion
提供的 Vue/React
工具库 axios-utils.ts
并拷贝到和 api-services
同级目录下:
Vue3
项目不能编译问题如果在 Vue3
项目中无法 编译通过,则需要修改根目录下的 tsconfig.app.json
和 tsconfig.vite-config.json
和 tsconfig.vitest.json
文件并添加下列配置即可,如:
"compilerOptions": {
"importsNotUsedAsValues": "remove",
"preserveValueImports": false
}
5.6.3.3 Angular
配置
点击 Generate Client
顶部菜单并选择 typescript-angular
进行下载。
下载成功之后拷贝下图选择的目录和文件:
接着打开你的 Angular
项目,并在 src
目录下创建 api-services
目录并将刚刚复制的目录文件放在里面。
接着下载 Furion
提供的 Angular
工具库 angular-utils.ts
并拷贝到和 api-services
同级目录下:
Angular
项目不能编译问题如果在 Angular
项目中无法编译通过,则需要修改根目录下的 api-services/encoder.ts
文件,并在 encodeKey
和 encodeValue
前添加 override
即可,如:
export class CustomHttpUrlEncodingCodec extends HttpUrlEncodingCodec {
override encodeKey(k: string): string {
k = super.encodeKey(k);
return k.replace(/\+/gi, "%2B");
}
override encodeValue(v: string): string {
v = super.encodeValue(v);
return v.replace(/\+/gi, "%2B");
}
}
最后在 src/app/app.module.ts
中注册 ServeModule
import { NgModule } from "@angular/core";
import { BrowserModule } from "@angular/platform-browser";
import { AppRoutingModule } from "./app-routing.module";
import { AppComponent } from "./app.component";
import { ServeModule } from "src/angular-utils";
@NgModule({
declarations: [AppComponent],
imports: [
BrowserModule,
AppRoutingModule,
ServeModule, // 注册代理服务模块
],
providers: [],
bootstrap: [AppComponent],
})
export class AppModule {}
5.6.4 初始配置
完成上面步骤之后还需要最后一步,那就修改服务端(后端)接口(API)地址。
5.6.4.1 Vue/React
配置
在 Vue/React
项目中编辑 Furion
框架提供的 axios-utils.ts
文件,并将 serveConfig
修改为对应的后端地址即可,如:
/**
* 当前版本:v1.0.3
* 使用描述:https://editor.swagger.io 代码生成 typescript-axios 辅组工具库
* 依赖说明:适配 axios 版本:v0.21.1
* 视频教 程:https://www.bilibili.com/video/BV1EW4y1C71D
*/
import globalAxios, { AxiosInstance } from "axios";
import { Configuration } from "./api-services";
import { BaseAPI, BASE_PATH } from "./api-services/base";
// 如果是 Angular 项目,则取消下面注释即可
// import { environment } from './environments/environment';
/**
* 接口服务器配置
*/
export const serveConfig = new Configuration({
// 如果是 Angular 项目,则取消下面注释,并删除 process.env.NODE_ENV !== "production"
// basePath: !environment.production
basePath:
process.env.NODE_ENV !== "production"
? "https://localhost:44342" // 开发环境服务器接口地址
: "http://furion.baiqian.ltd", // 生产环境服务器接口地址
});
// ......
5.6.4.2 Angular
配置
如果是 Angular
项目则编辑 Furion
框架提供的 angular-utils.ts
文件,并将 serveConfig
修改为对应的后端地址即可,如:
/**
* 当前版本:v1.0.3
* 使用描述:https://editor.swagger.io 代码生成 typescript-angular 辅组工具库
*/
import {
HttpClientModule,
HttpEvent,
HttpHandler,
HttpHeaders,
HttpInterceptor,
HttpRequest,
HttpResponse,
HTTP_INTERCEPTORS,
} from "@angular/common/http";
import { Injectable, NgModule } from "@angular/core";
import { finalize, Observable, tap } from "rxjs";
import { ApiModule, Configuration } from "./api-services";
import { environment } from "./environments/environment";
/**
* 接口服务器配置
*/
export const serveConfig = new Configuration({
basePath: !environment.production
? "https://localhost:44316" // 开发环境服务器接口地址
: "http://furion.baiqian.ltd", // 生产环境服务器接口地址
});
// ......
5.6.5 基本使用
5.6.5.1 Vue/React
中使用
在 Vue/React
中使用有两种方式,一种是 Promise
,另外一种就是 async/await
,推荐使用后者。
Promise
方式
import { getAPI } from "../axios-utils"; // 注意项目的路径
getAPI(SystemAPI) // SystemAPI 对应的是 Swagger 分组标签名称 + API
.apiGetXXXX()
.then((res) => {
var data = res.data.data!;
})
.catch((err) => {
console.log(err);
})
.finally(() => {
console.log("api request completed.");
});
async/await
方式
import { getAPI, feature } from "../axios-utils"; // 注意项目的路径
const [err, res] = await feature(getAPI(SystemAPI).apiGetXXX());
if (err) {
console.log(err);
} else {
var data = res.data.data!;
}
console.log("api request completed.");
关于文件流下载
对于文件流下载可能存在下载文件过大的情况,这时候需要添加 options
参数 responseType: "blob"
解决,如:
getAPI(SystemAPI, { responseType: "blob" }).apiGetXXX();
5.6.5.2 Angular
中使用
在 Angular
项目中,通过构造函数注入对应的服务即可
import { Component } from "@angular/core";
import { PersonService } from "src/api-services"; // 注意项目的路径
@Component({
selector: "app-root",
templateUrl: "./app.component.html",
styleUrls: ["./app.component.css"],
})
export class AppComponent {
title = "angulars";
// 注入 PersonService
constructor(private personService: PersonService) {}
ngOnInit(): void {
// 使用 personService
this.personService.apiPersonAllGet().subscribe({
next: (res) => {
// 请求成功
console.log(res);
},
error: (err) => {
// 请求失败
},
complete: () => {
// 请求完成
},
});
}
}
5.6.6 重新生成代码
如果后端接口(API)发生改变,只需要删除 api-services
下所有目录文件并重新生成复制进去即可。
Angular
项目如果是 Angular
项目,可以保留 api-services/encoder.ts
文件并删除其他目录文件,新生成的目录文件无需复制 encoder.ts
,这样可以避免每次修改 encoder.ts
文件。
5.6.7 Swagger
多分组生成
在很多大型系统中,为了方便对接口进行归类,往往使用了 Swagger
多分组功能,这样会使系统的接口散落在多个 swagger.json
中。
这个时候,需要在后端规范化文档中启用多分组配置:
{
"SpecificationDocumentSettings": {
"EnableAllGroups": true
}
}
启用配置之后在 Swagger
导航栏顶部下拉分组将出现 All Groups
选项,这时候使用这个 All Groups
的 swagger.json
进行生成。
5.6.8 自定义生成前端方法名
以下内容仅限 Furion 4.1.7+
版本使用。
通过我们根据 swagger.json
生成前端代码时,Swagger
会自动根据路由地址生成调用的 api
名称,但这样的名称往往不易读,这时候可自定义 [OperationId]
来配置生成的前端名称。
using Furion.SpecificationDocument;
public class PersonDto
{
[OperationId(nameof(TestMethod))]
public string TestMethod()
{
// ...
}
}