---
title: "08-项目笔记(一)"
created: 2025-12-02
tags:
- 项目
aliases:
- 项目笔记(一)
---
# 项目笔记(一)
## 一、项目概述
本项目是一个面向开发者的 API 平台,提供 API 接口供开发者调用。用户通过注册登录,可以开通接口调用权限,并可以浏览和调用接口。每次调用都会进行统计,用户可以根据统计数据进行分析和优化。管理员可以发布接口、下线接口、接入接口,并可视化接口的调用情况和数据。本项目侧重于后端,涉及多种编程技巧和架构设计层面的知识。
## 二、项目介绍
**一个提供 API 接口供开发者调用的平台:**
管理员可以发布接口,同时统计分析各接口的调用情况,用户可以注册登录并开通接口调用权限,浏览接口以及在线进行调试,并可以使用 SDK 轻松地在代码中调用接口。该项目前端简单,后端丰富,涵盖编程技巧和架构设计等多个技术领域。
**主页(浏览接口):**
![[yRl0pMDWmwqPfSQi-99b168d9.webp]]
**接口管理:**
![[x7SJUdgT5CGcK3az-526a58c8.webp]]
**在线调试:**
![[Pd7n53h3YspSmvq4-785dc9c0.webp]]
**使用自己开发的客户端 SDK,一行代码调用接口:**
![[7Bm7h4IFcUIjy9ot-55442611.webp]]
首先是它足够新颖,不同于大家在学校时做的管理系统、商城项目等,开放平台通常是知名企业(产品)才会建设和提供的。开放平台类的项目不要说现成的教程了,连相关的文章都少的可怜!
如下图,基本都是几年前的了:
![[EcQ7Fu4DrNXrqywG-42e165d5.webp]]
---
**💡** **为什么做这个项目?**
1. 该项目足够新颖,与大家在学校所做的管理系统、商城项目等有所不同。
2. 开放平台类的项目通常只由知名企业或产品提供,因此该项目的建设和提供也很特别。
3. 相比于其他技术领域,开放平台类的项目缺乏相关的教程和文章,相当匮乏。
---
开放平台项目涉及多个系统的交互,包含了 API 签名认证、网关、RPC、分布式等必学知识,很适合提升后端同学的系统设计和架构能力,是很多网课所不能提供的。
![[mKKsT50zqMdCfGxd-4272bcd5.webp]]
## 三、前置知识
1. 本项目更侧重后端,如果你学习过后端开发技术(比如 Java Web),希望做一个区别于管理系统的、有亮点的、写在简历上加分的项目,并提升自己的编程和架构设计能力,那么非常欢迎来学习!
2. 如果你是前端,也可以通过这个项目学习到快速开发前端项目的技巧,但是最好学习过 Vue 或 React 框架。
## 五、项目源码
1. API 开放平台 - [[前端代码](http://gitlab.code-nav.cn/root/yuapi-frontend)]
2. API 开放平台 - [[后端代码](http://gitlab.code-nav.cn/root/yuapi-backend)]
## 六、项目准备工具
**前端开发:**
1. [nodejs 安装教程](https://www.cnblogs.com/netcore5/p/15259499.html)
2. [WebStorm 安装教程](https://www.jb51.net/softs/598714.html) **或** [
[vscode 安装教程](https://blog.csdn.net/qq_44757223/article/details/122676919)or [vscode 安装教程](https://www.baoge.net/article/49252.html)]
(选其一即可)
**后端开发:**
1. [idea 安装教程](https://www.exception.site/essay/how-to-free-use-idea-202021-by-resigter-code)
2. [Java 安装教程](https://blog.csdn.net/qq_26552691/article/details/94598788)
3. [maven 安装教程](https://www.cnblogs.com/jywang/p/15828707.html)
**其他工具:**
1. [mysql 安装教程](https://www.cnblogs.com/itcui/p/15511683.html)
2. [redis 安装教程](https://www.cnblogs.com/luoyihao/p/16793388.html)
3. [yarn 下载](https://yarnpkg.com/downloads/1.22.15/yarn-1.22.15.msi)
4. [yarn 安装教程](https://blog.csdn.net/csdn_yudong/article/details/82015885)
5. [git 安装教程](https://blog.csdn.net/weixin_47638941/article/details/120632890)
**注意:**
1. nodejs 要下载大于 16.14,小于 18 的版本
2. idea要下载 2020.1以上的版本,才能下载 MyBatisX 插件
推荐的可能不是最优的,大家可以根据自己的需求去百度查找相关教程<( ̄︶ ̄)↗[GO!]
## 七、项目相关学习
**学习路线:**
1. 编程导航 - [前端学习路线](https://www.code-nav.cn/course/1789189862986850306/section/1789190394078011393?type=)
2. 编程导航 - [Java 学习路线](https://www.code-nav.cn/course/1789189862986850306/section/1789190431398928386?type=)
3. 编程导航 - [数据库学习路线](https://www.code-nav.cn/course/1789189862986850306/section/1789190581420793858?type=)
4. 编程导航 - [redis 学习路线](https://www.code-nav.cn/course/1789189862986850306/section/1789190541746872321?type=)
5. 编程导航 - [git&github 学习路线](https://www.code-nav.cn/course/1789189862986850306/section/1789190804671012866?type=)
## 八、本期时间点
经过7场直播,总时长近20小时。
![[vaOLiaZnwlWxweWM-d08d2f56.webp]]
### 预计计划
1. 项目介绍、业务流程、项目计划、需求分析
2. 数据库表设计
3. 前后端项目初始化(包含 Ant Design Pro 框架最新版本的使用)
4. 前后端代码自动生成(强烈推荐,提高 1000% 的开发效率)
5. 登录页、接口信息页开发
## 九、项目业务流程(时间点 15:12-35:10)
### 1. 流程概述
**5个子系统:**
1. **模拟接口系统**:提供各种模拟接口供开发者使用和测试,例如,提供一个随机头像生成接口。
2. **后台管理系统**:管理员可以发布接口、设置接口的调用数量、设定是否下线接口等功能,以及查看用户使用接口的情况,例如使用次数,错误调用等。
3. **用户前台系统**:提供一个访问界面,供开发者浏览所有的接口,可以购买或开通接口,并获得一定量的调用次数。
4. **API 网关系统**:负责接口的流量控制,计费统计,安全防护等功能,提供一致的接口服务质量,和简化 API 的管理工作。
5. **第三方调用 SDK 系统**:提供一个简化的工具包,使得开发者可以更方便地调用接口,例如提供预封装的 HTTP
请求方法、接口调用示例等。
**需要考虑的关键问题和挑战:**
1. **接口设计**:需要设计清晰易用的 API 接口,并且提供详细的接口文档,以方便开发者使用。
2. **性能和可用性**:平台需要承载大量的接口请求,因此需要考虑到性能和可用性问题。例如,设计高效的数据存储和检索策略,确保
API 网关的高性能等。
3. **安全**:平台需要防止各种安全攻击,例如 DDOS 攻击,也需要保护用户的隐私和数据安全。
4. **计费和流量控制**:需要设计合理的计费策略和流量控制机制,以确保平台的稳定运行和收入来源。
5. **易用性和用户体验**:需要为开发者提供简单易用的接口调用工具和友好的用户界面,提供优质的用户体验。
![[CIxO4EJ8hjLC4mmF-e7ffc99c.webp]]
### 2. 详细说明
**需要注意的问题及知识:**
❗ **注意:**在实现接口调用时,如果要作为一个开放平台,不要轻易免费开放接口,因为这会极大地增加平台被攻击的风险,导致服务器瞬间被 DDOS
攻击瘫痪。
ps.不了解的朋友看看🐟被 ddos 的视频🐶
💡 **在调用这个接口时需要考虑以下问题:**
1. 需要考虑访问权限的问题:用户是否可以随意访问数据库和接口。
2. 需要添加计费功能,统计用户调用次数,并考虑限流或流量保护措施。
3. 需要考虑如何有效地管理用户。(例如,如果用户欠费或者是陌生人调用接口,我们需要及时发现并采取措施)
🪔 **有没有听说过 API 网关?**
API 网关的主要作用是为多个接口提供保护,并集中进行计费、健全日志等逻辑处理。类似于我们去火车站坐高铁一样,无论是去哪个站台坐哪个火车,都需要先通过检票口进行集中的检票。
与我们程序中的 AOP 不同,API 网关是一个独立的服务,需要单独开通。
🪔 **什么是 SDK?**
SDK 是软件开发工具包的缩写,是一种为软件开发者提供支持的一系列工具、接口和规范的集合。举个例子,比如腾讯云提供了一些接口,比如创建 VPC
等,如果开发者直接向腾讯云服务器发请求,需要输入密钥、做签名认证等操作,非常繁琐。因此,在构建第三方 API 平台时,一般都会提供一套 SDK,让使用者能够轻松地调用接口,无需自己编写和封装
HTTP 请求。可以把 SDK 理解为 Java 语法中的工具包,使用者只需要最少量的代码即可调用接口,如果不理解 SDK,需要加强 Java
语法的学习。
---
首先,管理员可以发布接口并将其存储到数据库中,设置接口的调用数量和上下线接口状态。例如,发布一个免费的随机头像接口,它将有一个地址(比如:yupi.icu/img)并保存到数据库中,这里需要一个后台管理系统。
现在,我们的用户是开发人员,他们如何使用这个接口呢?为此,我们需要提供一个访问界面,我们称其为用户前台。用户通过前台浏览所有的接口,当找到所需接口时,可以购买或开通接口,获取一定量的调用次数,例如一万次或一千次等。开通接口后,开发者可以调用接口的
API,系统从数据库中获取相关数据并进行调用;同时,管理员可以在后台进行相关的操作(如下图所示)。
![[8uaNqYeQDxR83efU-8824ba92.webp]]
举个例子,腾讯云提供各类开放接口,其中有他们自己开发的或者是与第三方合作开发的。而在该项目中,由于目前没有第三方,所以我们需要自己提供这些接口。因此,在该项目中,将会带领大家开发模拟接口——这是需要开发的第三个子系统,我们可以随意开发一些类似
helloworld 的小接口,然后将整个流程打通。
后续当你开发额外的接口时,可以自己开发并提供给用户。如果你使用了第三方接口平台,你需要向对方提供接口文档,就像腾讯云的 API,其中有很多功能接口,每个接口都需要提供相应的文档,否则用户将不知道如何使用这些接口。这种情况在使用第三方接口时很常见,有时用户看文档时感到困惑,觉得文档难懂或不够清晰。为解决这个问题,我们最好提供在线调试功能,让用户能够立即知道如何使用接口。此外,为了方便用户,我们可以提供一些示例代码,让他们可以直接复制粘贴到自己的项目中。因此,我们的项目中应该提供接口文档和在线调用功能(如下图所示)。
![[LqsAud6zOexqmplb-ac6fadc8.webp]]
![[245cfoRmnRisR3nV-df9b1158.webp]]
在作为第三方接口平台的时候,我们需要考虑到防止攻击、统计调用次数、计费流量保护等方面的问题。然而,如果我们需要给用户提供大量的接口,单独为每个接口开发计费统计、流量保护等功能可能会成为一项繁琐的工作。因此,我们可以考虑使用一些通用的解决方案,例如使用
API 网关来管理接口,统一进行流量控制、计费统计等操作。这样可以减少重复开发的工作量,提高开发效率。
目前,实现 API 网关的方式有多种,例如 Kong、Nginx、Gateway 和 Spring Cloud 的 Gateway 扩展等。企业中也可能会使用自建
API 网关。此外,Kong 实际上是对 OpenResty 的一种封装,同时也是对 Nginx 服务器的上层扩展,因此,熟练掌握 Nginx
服务器的使用将有助于理解 API 网关的实现。总的来说,熟悉 Nginx 服务器的开发人员应该更容易实现 API 网关。如果大家对这些技术还不太了解,那这个项目将会帮助大家拓宽视野。这些技术都是架构层面或微服务层面的技术,在企业实战层面是必不可少的。
这个项目需要包含多个子系统,但光有这些还不足以完成整个项目,因为我们还需要为其他开发者提供一个第三方接口调用平台。在这个平台中,我们将提供一个下载示例代码的
SDK,以便其他开发者可以更方便地使用我们的接口。因此,我们还需要开发一个小系统,即第三方接口调用的 SDK。
目前,我们已明确了该项目五个主要平台,分别是模拟接口、后台管理系统、用户前台、API 网关和第三方调用 SDK。而这五个平台会被拆分成五个项目,并且每个项目还会被拆分成相应的子系统。具体来说,我们需要开发五个系统:模拟接口系统、后台管理系统、用户前台系统、API
网关系统和第三方调用 SDK 系统。
![[CIxO4EJ8hjLC4mmF-e7ffc99c.webp]]
## 十、项目技术选型**(时间点 35:26-40:27)**
**前端:**
- React 18
- Ant Design Pro 5.x 脚手架
- Ant Design & Procomponents 组件库
- Umi 4 前端框架
- OpenAPI 前端代码生成
**后端:**
- Java Spring Boot
- MySQL 数据库
- MyBatis-Plus 及 MyBatis X 自动生成
- API 签名认证(Http 调用)
- Spring Boot Starter(SDK 开发)
- Dubbo 分布式(RPC、Nacos)
- Swagger + Knife4j 接口文档生成
- Spring Cloud Gateway 微服务网关
- Hutool、Apache Common Utils、Gson 等工具库
## 十一、需求分析**(时间点 04:08-09:30、50:32-51:13)**
**背景:**
1. 前端开发需要用到后台接口
2. 使用现成的系统的功能 - [免费API接口平台](http://api.btstu.cn/)
**做一个 API 接口平台:**
1. 管理员可以对接口信息进行增删改查
2. 用户可以访问前台,查看接口信息
**其他要求:**
1. 防止攻击(安全性)
2. 不能随便调用(限制、开通)
3. **统计调用次数**
4. 计费
5. 流量保护
6. API 接入
## 十二、本期计划(时间点 **51:25-53:40**)
1. 项目脚手架搭建(初始化项目) -- 10 分钟前端,5 - 10 分钟后端
2. 管理员可以对接口信息进行增删改查
3. 用户可以访问前台,查看接口信息
## 十三、前端项目初始化(时间点 53:40-**01:13:14**)
### 1. 搭建项目脚手架(时间点 55:01-58:06)
#### 1.1 点击开始使用
Ant Design Pro:[官方文档](https://pro.ant.design/zh-CN/)
进入 Ant Design Pro 网站,点击`开始使用`。
![[UO9aAB0sEcuJxkZt-bab3d839.webp]]
#### 1.2 复制初始化命令
复制初始化命令。
![[jOifEivGX4bZDJpE-d35926eb.webp]]
```bash
# 使用npm npm i @ant-design/pro-cli -g pro create myapp
```
#### 1.3 在本地打开项目目录
在 D 盘按[Ctrl+Shift+N]新建文件夹,位置和项目名可自拟。
进入创建好的项目目录,点击左上方目录栏。
![[q6AEbbXDLAtg0EkF-b91744f5.webp]]
输入`cmd`后,点击[Enter]回车,进入到当前目录的终端。
![[630u40pcGxajERvI-1644c1e3.png]]
**或使用**`**PowerShell**`**(在 cmd 使用 npm 卡顿,换成 PowerShell 试试):**
在项目目录中空白处,按 [Shift+鼠标右键] → 选择`在此处打开Powershell`。
![[vCnJq1XnX5N3W5vN-1bf19512.webp]]
#### 1.4 使用初始化命令
打开`Powershell`。
![[RXmuXnMIW4R9qnVS-6b14115e.png]]
将初始化命令粘贴到此处,回车,全局安装脚手架。
![[VardodlOT28ZDBtT-4496039b.webp]]
#### 1.5 创建项目
使用`pro create yuapi-frontend`创建项目。
ps.`yuapi-frontend`是项目名称,可自行更改。
选择最新版本`umi@4`。
![[vdyOLPuOqj56bWyo-48a0ec50.webp]]
### 2. 使用前端开发工具(时间点 58:06-01:05:43)
#### 2.1 打开项目
开发前端使用`webstorm`或`vscode`。
ps.🐟用`webstorm`,电脑内存太少,再装`webstorm`要炸,我这边就使用`vscode`。
进入`vscode`。
![[xV2D2aki39EccJme-6d42aba7.webp]]
点击左上角 文件 → 打开文件夹**。**
![[Pw5ip23FRNZjdTXf-bd07df29.webp]]
选择创建好的项目目录。
![[pmQPFmGjYbYy0OsR-89033893.webp]]
以下是刚刚生成的项目(如下图所示):
![[cUjzaDqKJ1GSd4va-c2d37a7d.webp]]
**确认环境是否匹配:**
右键目录栏空白处,选择在集成终端中打开。
![[g2G8rAFeI071kpud-97d5501a.webp]]
输入命令,查看版本。
![[xt0DbGd0tYsBn9Cc-2d6f3f8f.webp]]
```text
# 查看nodejs版本 node -v # 查看npm版本 npm -v # 查看yarn版本 yarn -v
```
ps. nodejs 建议下载大于 16.14,小于 18 的版本。
#### **2.2 安装依赖包**
打开开发工具下的终端,输入`yarn`或`yarn install`。
ps.依赖包没有安装,启动不了项目。
`webstorm`:
点击最下方的`Trtminal`(终端)。
![[NiHCC8IgUBWjkKxD-93c3dbbe.webp]]
在终端输入`yarn`。
![[IbTUY41pyzNdpV7H-0bc1fd18.webp]]
就可以在项目目录中看到生成好的依赖包。
![[j9lftokBlxjrlVrm-18b68c2b.webp]]`vscode`:
在终端输入`yarn`。
![[EbCuyrLkUYqb6Cq1-b1f498ac.webp]]
就可以在项目目录中看到生成好的依赖包。
![[RWoHq1xs0cTpN8ZS-3857e211.webp]]
#### 2.3 项目试运行
找到`package.json`中的`dev`,以本地开发的方式运行。
ps.`package.json`:管理项目依赖、脚本的工具。
![[UDm5FliuepAvCHP7-e27ae352.webp]]
---
💭 这里的`dev命令`和`start:dev命令`一样都是启动命令。
`start:dev命令`中的`MOCK=none`,是把自动模拟的数据关闭;
`dev命令`是调用自己定义的后端;
`start命令`是以模拟数据的方式运行项目。
---
`webstorm`:
点击绿色启动按钮。
![[7Cx3VsBF98y3ijua-ecd641bc.webp]]
配置好了之后点击`Run`。
![[xh5s5YghoKEV2CjO-6d80014e.webp]]
或在终端使用`yarn run dev`启动。
![[UOvXJ7F6FtDe50Q0-eaca6469.webp]]
`vscode`:
在终端使用`yarn run dev`启动。
![[6HzJTHOWV43430Uw-46df5f38.webp]]
根据提示,访问 [http://localhost:8000,预览一下:](https://www.codefather.cn/course/1790979723916521474/section/1790982983314354177?type=#heading-0http://localhost:8000%EF%BC%8C%E9%A2%84%E8%A7%88%E4%B8%80%E4%B8%8B%EF%BC%9A)
![[GRaMO1GGCQp77rX5-00186b31.webp]]
按照提示,输入账号和密码。
```text
# admin会多一个管理页页面 用户名:admin 或者 user 密码:ant.design
```
在`dev`模式下,由于本地没有后端服务器,因此会提示登录失败。
因为缺少数据,无法运行ㄟ( ▔, ▔ )ㄏ
![[aAFY2lkhQ2CElvIU-8e458be6.webp]]
找到`package.json`中的`start`,以模拟数据的方式运行。
`webstorm`:
点击绿色启动按钮。
![[bb1seUXAarLMTOeM-48a93f77.webp]]
配置好了之后点击`Run`。
![[BFsEn3h4HolekmqW-5c016bb8.webp]]
`vscode`:
在终端使用`yarn run start`启动。
![[ry2KQZq58W6w1Zaw-5e7abc96.webp]]
访问 [http://localhost:8000,再次输入用户名和密码,登录成功。](https://www.codefather.cn/course/1790979723916521474/section/1790982983314354177?type=#heading-0http://localhost:8000%EF%BC%8C%E5%86%8D%E6%AC%A1%E8%BE%93%E5%85%A5%E7%94%A8%E6%88%B7%E5%90%8D%E5%92%8C%E5%AF%86%E7%A0%81%EF%BC%8C%E7%99%BB%E5%BD%95%E6%88%90%E5%8A%9F%E3%80%82)
![[L8Q1WklEOsMY0LoC-4c5021c5.webp]]
### 3. 项目瘦身(时间点 01:05:43-01:13:14)
这个项目文件夹内有很多文件和代码,能不能减少一些不必要的内容呢?
![[CakLaOXpGJGoYVdI-4edc1709.webp]]
#### 3.1 移除国际化
Ant Design Pro 集成了很多的功能。比如:国际化。
国际化的代码在 `目录路径: /src/locales` 下:
![[RHEYhKXhJPdCBFni-1fd57928.webp]]
---
国际化介绍:
它给我们集成了多种语言:中文、英文...
因为这个项目主要在国内访问,国内都没多少用户浏览,更别说国外了;
如果要使用国际化,还要去配置不同语言,很麻烦。直接移除🔨
---
在`package.json`找到`i18n-remove`,这里提供了移除国际化的脚本。
![[rrNFTypYYeFspX6L-845ab259.webp]]
执行`i18n-remove`,报错了。
![[vDW96YGOFzu7067A-69b6377b.webp]]
**遇到报错怎么办?**
1. 百度
2. 问原作者 or 官方团队 👉 [官方的开源地址](https://github.com/ant-design/ant-design-pro)
![[qtd6s4x4dZ0uoLS6-d4bff511.webp]]
然后看刚刚报错的地方,找几个报错信息,多搜索几次,能找到为止。
复制`pro i18n-remove --locale=zh-CN --write`报错信息。
![[95VXulRxEeXbMYzq-b8746532.webp]]
点击`Issues`,将报错信息粘贴至搜索框搜索,回车。
![[sffKd4iojqYzmDU2-b8914eca.webp]]
搜索到了 (≧∀≦)ゞ
![[kD6Kzo4h00PB1KGm-1be6a9b3.webp]]
往下滑,一模一样的报错,报错路上不是一个人hhhhh
![[7ogIxE4721QRhKEA-45fdd19e.webp]]
再往下滑,有人提供[解决方法](https://github.com/ant-design/ant-design-pro/issues/10452)(如下图所示)。
![[z947zesInA3r1Qc1-02762564.webp]]
![[89D3WpmfHH2zE9Es-2dbdadb7.webp]]
```text
1.执行 yarn add eslint-config-prettier --dev yarn add eslint-plugin-unicorn --dev 2.然后修改node_modules/@umijs/lint/dist/config/eslint/index.js文件注释 // es2022: true可以解决问题
```
在终端执行:
```text
yarn add eslint-config-prettier --dev yarn add eslint-plugin-unicorn --dev
```
![[WLQdrjRi6DrI7rwH-4beb69d0.webp]]
注释掉`项目目录:node_modules/@umijs/lint/dist/config/eslint/index.js`文件中的`es2022: true`(一定要注释掉,不然还会报错👻)
![[gPKWRwuzslc97OMD-9a617562.webp]]
一定要注释掉,不然还会报错。
![[CQ4w652Hr1zQqFIr-d6c28a0f.png]]
再次执行`i18n-remove`,可以看见它帮我们删除了一些代码。
![[jT1joMmxX5yTOF9Z-fa4605e2.webp]]
但是`locales目录`不会被删掉,我们手动将其删除。
![[st62UfAvFOs0CNiU-bc2e9559.webp]]
在终端使用`yarn run start`启动项目,看看能不能正常运行。
![[ry2KQZq58W6w1Zaw-5e7abc96.webp]]根据提示,访问 [http://localhost:8000,发现原本右上角的语言切换消失了:](http://localhost:8000%EF%BC%8C%E5%8F%91%E7%8E%B0%E5%8E%9F%E6%9C%AC%E5%8F%B3%E4%B8%8A%E8%A7%92%E7%9A%84%E8%AF%AD%E8%A8%80%E5%88%87%E6%8D%A2%E6%B6%88%E5%A4%B1%E4%BA%86%EF%BC%9A)
![[4iSVl3sFeyRUmLnu-cdefe077.webp]]
登录之后发现原本左侧的菜单栏没了。
![[YmNxbIfiSZh9g7Ks-46efed92.webp]]
给`目录路径:config/route.ts`的路由加`name`,
![[FzClIj3QRoEzJc8t-65f9c75c.webp]]
给路由加上`name`。
![[dPciDcGsBDDEDshY-49a71e20.webp]]
再次启动项目,输入账号密码后,菜单栏显示出来了。
![[DspJ3vDWp9hYxqS3-8d33f132.webp]]
#### 3.2 移除测试工具
![[m28Dg7x4VnQXykzF-8f9efd18.webp]]
## 十四、后端项目初始化(时间点 01:13:34-02:00:48)
### 1. 使用后端项目模板(时间点 01:13:40-**01:22:34**)
#### 1.1 下载模板
使用🐟整的万用后端项目模板 👉 [springboot-init 项目](https://www.code-nav.cn/course/1789200994552225793/section/1789201346202673154?type=)。
![[HyCUu50XL2eJmKx2-abdf5e1b.webp]]
下载并解压至项目目录。
![[wSKKpTDNuZwOxXvz-f0ef1243.webp]]
并修改文件夹名为`yuapi-backend`。
![[qnMx9vFCQ68yGPO9-bc75d263.webp]]
**模板功能:**
![[xBqQvkVOJop38C8T-1c1e906f.webp]]
#### 1.2 加载依赖
**开发后端使用**`**idea**`**:**
用`idea`打开`yuapi-backend`。
![[o2zEyquDsqdr6ZZZ-5dc45592.webp]]
选择左上角`File→settings`。
![[hcu4OAiiSSyk7pgO-8e38efec.webp]]
修改 maven 的设置,我这里默认会变成在 C 盘。
![[83alY0EW0fDi0JTc-fdc41629.webp]]
然后项目就开始加载依赖,最下方,有个蓝条咻咻地在动,等它加载完。
点击`pom.xml`,看看有没有报错。
![[7QqDtbTKeGEWJuh3-6a0de090.webp]]
有报错就点击右侧菜单栏的 maven。
![[iRnRxd6uo9AKgdvk-1e9e590e.webp]]
点击
![[vbhsYZpx7qYQEnTH-e6844250.png]],让它再加载一下。
![[tfY6eoy3OQmyt7Iz-cec6e38d.png]]
如果还有报错,找到报错的位置,没有版本号就添加版本号,有版本号就换一个。
![[Kp1gmCNYi90IlBHj-b748de12.webp]]
怎么知道这个依赖有什么版本呢?随便加一个?(达咩.jpg)
百度搜索 [maven repository](https://mvnrepository.com/),这个网站能找到所有
java 的包。
![[DNG8Pnm9wSqtTHJi-28c3cf90.webp]]
把报错的位置复制粘贴到搜索框,回车,然后点击搜索到的内容。
![[RfJNUbaNd8gyYhcH-39b1681d.webp]]
这里有很多版本,选一个风险少,人数多,版本不会太新,或者太旧的;
点击选择的版本号。
![[dqPb72pmcwpJ58cK-fe15acc2.webp]]
复制依赖。然后粘贴至`pom.xml`,替换掉之前报错的。
ps.希望不会和其他依赖的起冲突,先给 xdm 试试水 保佑.jpg
![[SWLFUVNnEGx2aELJ-2c7cf674.webp]]
```xml
org.springframework.boot spring-boot-maven-plugin 2.7.2
```
粘贴完之后点击
![[eLSpULEDTHKg1VGU-68e34a84.png]],或者点击右侧菜单栏的 maven 中的
![[blwgIh8WKY1m9BgY-73a309af.png]],让它再加载一下(推荐后者)。
![[1sT1IAE1tDBDOCoc-47f953ed.webp]]
没有报错,问题解决<( ̄︶ ̄)↗[GO!]
![[jPVvYEM3tGicYUfK-0937a682.webp]]
#### 1.3 下载 markdown 插件(已下过可跳过)
idea 下载插件有种方式:
1. 通过 idea 软件提供的方式下载
2. 外部安装导入 - 直接安装
---
这里我们使用第一种方法。
点击左上角菜单栏上的 File → settings → plugins。
![[PdtRzr1UAFLb8G52-c98b80ee.webp]]
在搜索框输入`markdown`,点击[Enter]回车(步骤如下图所示)。
![[591oqA6EM1cmsqIP-232da824.webp]]
安装这个插件之后,打开 .md 文件就可以支持 md 的视图查看和编辑内容。一般用于写 README.md 文件。
点击后端模板中的 README.md,这里是🐟写的使用教程,右上角有三个图标:
1. ![[IRe6oWoy355u9cpb-4b7b388a.png]]Editor only(只编辑),如下图所示。
![[Cp4TYfZ8KeBWKp5D-2878380b.webp]]
1. ![[6GKitvJd21JM1PWV-002b2ba2.png]]Editor and Preview(编辑和预览),如下图所示。
![[ZNmUx7WDyJjpZKeh-9b200c8f.webp]]
1. ![[9EOT23mDxEfsAi23-bab43b82.png]]Preview only(只预览),如下图所示。
![[iKwxLmqdWNdqEx1e-955bd7e7.webp]]
#### 1.4 修改项目名称(时间点 01:15:54-**01:16:20**)
进入`pom.xml`;
选中`springboot-init`然后按住[Ctrl+Shift+R]全局搜索。
![[V89vyBfeheoh9kNb-2fa56209.webp]]
把`springboot-init`全部替换成`yuapi-backend`。
![[OS6yvoQD3io7SW56-166c8e92.webp]]
把描述改成`鱼皮接口`。
![[hWZs6zEHkZyeXQzu-15e25ca1.webp]]
点击右侧工具栏上`Maven`,点击
![[aDcyktU6tIUCIdXr-b2c06c22.png]]刷新依赖。
![[HsXiUcCmv2WShrfu-442f7ab5.webp]]
项目名称更新成功。
![[JIDYTwY9NHR8GGs4-41828b70.png]]
#### 1.5 修改配置(时间点 01:16:20-01:18:57)
##### 1.5.1 idea 连接本地数据库
首先点击右侧菜单栏的`Database`。
![[JXN525Tup3sHNn47-e25cd46f.webp]]
点击
![[gUtHN7drX1ctMkDE-360d4bc9.png]]→`Data Source`→ `Mysql`。
![[z8xAh2fjk0edSHQt-528b0c51.webp]]
填写 mysql 用户名和密码。
![[0wCCnRCCom7LFdcl-d8e47f87.webp]]
连接成功。
![[xrvJIF8uTPL0NDOs-40b2f10c.webp]]
这个模板提供了示例 — 初始化的数据库脚本,找到`ddl.sql`;
上方会弹出是否使用 Mysql 语法,点击`Use Mysql`,这个文件就会以 Mysql 语法格式显示。
![[hGrNYMiN6XMn1kvU-5393737a.webp]]
```sql
-- 创建库 create database if not exists my_db; -- 切换库 use my_db; -- 用户表 create table if not exists user ( id bigint auto_increment comment 'id' primary key, userName varchar(256) null comment '用户昵称', userAccount varchar(256) not null comment '账号', userAvatar varchar(1024) null comment '用户头像', gender tinyint null comment '性别', userRole varchar(256) default 'user' not null comment '用户角色:user / admin', userPassword varchar(512) not null comment '密码', createTime datetime default CURRENT_TIMESTAMP not null comment '创建时间', updateTime datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间', isDelete tinyint default 0 not null comment '是否删除', constraint uni_userAccount unique (userAccount) ) comment '用户'; -- 帖子表 create table if not exists post ( id bigint auto_increment comment 'id' primary key, age int comment '年龄', gender tinyint default 0 not null comment '性别(0-男, 1-女)', education varchar(512) null comment '学历', place varchar(512) null comment '地点', job varchar(512) null comment '职业', contact varchar(512) null comment '联系方式', loveExp varchar(512) null comment '感情经历', content text null comment '内容(个人介绍)', photo varchar(1024) null comment '照片地址', reviewStatus int default 0 not null comment '状态(0-待审核, 1-通过, 2-拒绝)', reviewMessage varchar(512) null comment '审核信息', viewNum int not null default 0 comment '浏览数', thumbNum int not null default 0 comment '点赞数', userId bigint not null comment '创建用户 id', createTime datetime default CURRENT_TIMESTAMP not null comment '创建时间', updateTime datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间', isDelete tinyint default 0 not null comment '是否删除' ) comment '帖子';
```
这个模板默认的数据库名为:`my_db`,改成自己的;
按[Ctrl+Shift+R]全局搜索,把数据库名全部替换成`yuapi`。
![[U0kJl53gMxoPj12h-dd982d90.webp]]
把光标放到文件中,按 [Ctrl+A] 全选 → 点击左上角
![[y48s7BSY2DXstyKO-b5786ca0.png]]运行。
![[8VBny7CmxIiGYiWm-d00d0add.webp]]
右侧就多了一个创建好的`yuapi`数据库。
![[4SvLj1DCMYQE8wvi-13898524.webp]]
找到`application.yml`,修改数据库配置为自己的。
![[YHeq2x43etHAOtvT-81a642bb.webp]]
##### 1.5.2 修改 redis 配置
在`application.yml`中还用到了`redis session`;
如果没有用到 redis,可以把`store-type`改成`none`→ 即存储会话不用 redis
存储。
我本地有 redis,所以就不改为 none 了🐶
![[TJfl9h3A7o0ylcH4-add7220e.webp]]
注意:不改 none 的小伙伴,一定要本地先启动 redis;不然,报错警告👻。
在本地安装的 redis 目录下找到 `redis-server.exe`,双击启动,放那别关掉。
![[1kktHuCeghRT0poC-f2f6f720.webp]]
#### 1.6 项目试运行**(时间点 01:18:57-01:22:34)**
找到启动类`MyApplication`,点击左侧
![[oeKNqum1TnpluIlC-c0571a90.png]]绿色启动按钮;
下方控制台会提示已经在`7529`端口启动成功了。
![[mFjLSVgbNMi6b8pD-db0033e9.webp]]
这个端口号是在`application.yml`配置中设置的,可自行更改。
![[9QyQHEXEjEJyqTfa-bdcb546e.webp]]
访问 [http://localhost:7529/api/doc.html,打开现成的接口文档;](http://localhost:7529/api/doc.html%EF%BC%8C%E6%89%93%E5%BC%80%E7%8E%B0%E6%88%90%E7%9A%84%E6%8E%A5%E5%8F%A3%E6%96%87%E6%A1%A3%EF%BC%9B)
不需要写前端就能在线调试接口,这里有很多现成的接口。
![[9k9anOQxVKIqz86p-c8208e1d.webp]]
**部分接口介绍:**
使用`userRegister`接口注册账户。
![[2XLE1nBnkYTW2Qk2-47d31e23.webp]]
注册成功。
```json
{ "checkPassword": "12345678", "userAccount": "yupi", "userPassword": "12345678" }
```
在`userLogin`接口中使用刚刚注册的账号进行登录。
![[cgzXyAW1cSVVM5T6-4f2842d5.webp]]
登录成功。
```json
{ "userAccount": "yupi", "userPassword": "12345678" }
```
使用`listUser`接口获取所有用户。
![[nSAUjpjK3nAXGPsZ-c2fd83ef.webp]]
使用`getLoginUser`接口获取当前登录的用户。
![[o0RPC5JKTcGto5ta-0d6d2f72.webp]]
使用`listPost`接口获取帖子信息。
![[hGACqyDEpwagEl6S-78a0453a.webp]]
现在还没有权限,因为是获取全量数据 — 指全部的数据 list 接口,只有管理员有这个接口的权限。
### 2. 数据库表设计(时间点 01:23:45-01:42:23)
任何系统实际上都可以抽象成管理系统。那么对于我们的系统,最基本的功能就是接口管理。
| **接口信息表(interface\_info)** | | |
| --- | --- | --- |
| **字段** | **说明** | **类型** |
| id | 用户 id(主键) | bigint |
| name | 名称 | varchar(256) |
| description | 描述 | varchar(256) |
| url | 接口地址 | varchar(512) |
| requestHeader | 请求头 | text |
| responseHeader | 响应头 | text |
| status | 接口状态(0-关闭,1-开启) | int |
| method | 请求类型 | varchar(256) |
| userId | 创建人 | bigint |
| createTime | 创建时间 | datetime |
| updateTime | 更新时间 | datetime |
| isDelete | 是否删除(0-未删, 1-已删) | tinyint |
```sql
-- 接口信息 create table if not exists yuapi.`interface_info` ( `id` bigint not null auto_increment comment '主键' primary key, `name` varchar(256) not null comment '名称', `description` varchar(256) null comment '描述', `url` varchar(512) not null comment '接口地址', `requestHeader` text null comment '请求头', `responseHeader` text null comment '响应头', `status` int default 0 not null comment '接口状态(0-关闭,1-开启)', `method` varchar(256) not null comment '请求类型', `userId` bigint not null comment '创建人', `createTime` datetime default CURRENT_TIMESTAMP not null comment '创建时间', `updateTime` datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间', `isDelete` tinyint default 0 not null comment '是否删除(0-未删, 1-已删)' ) comment '接口信息';
```
#### 2.1 SQL 之父使用(时间点 01:25:09-01:39:56)
[**SQL 之父**](http://sqlfather.yupi.icu/) **简单介绍:**
在开发新项目时,我们通常需要编写 SQL 语句来创建数据库表并手动添加假数据进行测试。然而,随着项目的增多和表格的复杂度,手动编写和添加数据的成本越来越高,也越来越浪费时间。
SQL 之父可以帮助我们自动生成 SQL 语句和模拟数据,从而显著提高开发效率,让我们更专注于业务逻辑的实现。
👋🏻 做新项目用新表、模拟数据,可以用 SQL 之父,这里还能转换成 Java 代码。
![[QLryf2F4XcdqWFFS-7475f357.webp]]
我们要创建一个接口信息表,在SQL之父填写对应信息。
![[s2IMi3od7NScfMwH-b71b8a06.webp]]
修改通用字段,字段采用驼峰式,让前后端一致。
![[aT3doP9Ke5HY3CAG-cca378bc.webp]]
除了这四个字段之外,还需要一个接口名称字段`name`。
接口还需要哪些字段?
建议:如果不确定接口要有什么信息,就去看一下 Swagger 接口文档里都有啥。
![[fWTsz2VxGFEN3E5o-5b469b10.webp]]
![[O3BvNI1au465Tgut-00b59eeb.webp]]
这些都可以作为一些字段信息。
**目前需要的字段:**
![[VKMyNRETVKga5mkJ-50002172.png]]
还需要哪些字段呢?可以去看看请求的信息;
鼠标右键 → 选择`检查`(或按`F12`),点击网络,按`F5`刷新,查看请求的信息。
![[ZeHZN45MrqTWTf3h-8464728a.webp]]
这些请求头,响应头,你都要了解他们是做什么的,都是库表设计要写进去的。
ps.例如:cookie、Content-Type、Referer、Host、请求方法、状态代码等等。
![[tYqh1TA729ydiDwu-53414998.webp]]
**新增字段:**
![[lzcX63bJkb0b51X3-f686bcbb.png]]
回到SQL之父填写对应字段。
![[03ADK1IYszK05rYp-14d5ed7d.webp]]
可点击左侧 > 按钮设置对应字段。
![[YkwH4KGFJrhLPqI0-963534f2.webp]]
设置字段(设置完后,点击`一键生成`):
![[VVfqzsOkf7bO90n9-32335650.webp]]
![[ZP1be9JT5NMfJWER-c2ab611c.webp]]
![[aw4o88TiQLsBE2kU-070ced71.webp]]
![[vQfwFZsJ3ptIYAFT-b58b6268.webp]]
此时右侧会出现建表语句和插入语句;上方是建表语句,下方是插入语句(模拟数据)。
这里程序有个小 bug,把 CURRENT\_TIMESTAMP 两边的单引号去掉。
ps.不然,建表报错警告👻
![[V3XQe6RVlQC58bjF-e3180e41.webp]]
点击`复制全部`。
![[SpeChhK4piBGsfUb-669beaf3.webp]]
```java
-- 接口信息 create table if not exists yuapi.`interface_info` ( `id` bigint not null auto_increment comment '主键' primary key, `name` varchar(256) not null comment '名称', `description` varchar(256) null comment '描述', `url` varchar(512) not null comment '接口地址', `requestHeader` text null comment '请求头', `responseHeader` text null comment '响应头', `status` int default 0 not null comment '接口状态(0-关闭,1-开启)', `method` varchar(256) not null comment '请求类型', `userId` bigint not null comment '创建人', `createTime` datetime default CURRENT_TIMESTAMP not null comment '创建时间', `updateTime` datetime default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP comment '更新时间', `isDelete` tinyint default 0 not null comment '是否删除(0-未删, 1-已删)' ) comment '接口信息'; insert into yuapi.`interface_info` (`name`, `description`, `url`, `requestHeader`, `responseHeader`, `status`, `method`, `userId`) values ('刘笑愚', '吴琪', 'www.wesley-trantow.com', '万鸿煊', '王越彬', 0, '黄雪松', 26826); insert into yuapi.`interface_info` (`name`, `description`, `url`, `requestHeader`, `responseHeader`, `status`, `method`, `userId`) values ('潘展鹏', '叶晓啸', 'www.courtney-kassulke.net', '戴鸿煊', '袁荣轩', 0, '谢立诚', 434); insert into yuapi.`interface_info` (`name`, `description`, `url`, `requestHeader`, `responseHeader`, `status`, `method`, `userId`) values ('覃天宇', '冯昊强', 'www.johnie-harris.name', '武博文', '戴思聪', 0, '毛昊焱', 2334); insert into yuapi.`interface_info` (`name`, `description`, `url`, `requestHeader`, `responseHeader`, `status`, `method`, `userId`) values ('谭黎昕', '傅哲瀚', 'www.josette-adams.org', '覃振家', '吕风华', 0, '孔鹭洋', 57553); insert into yuapi.`interface_info` (`name`, `description`, `url`, `requestHeader`, `responseHeader`, `status`, `method`, `userId`) values ('魏嘉熙', '沈思聪', 'www.wyatt-nader.org', '韩嘉懿', '熊思源', 0, '姚立轩', 0); insert into yuapi.`interface_info` (`name`, `description`, `url`, `requestHeader`, `responseHeader`, `status`, `method`, `userId`) values ('龚修洁', '宋锦程', 'www.jerlene-grimes.io', '廖哲瀚', '张建辉', 0, '林天宇', 34618); insert into yuapi.`interface_info` (`name`, `description`, `url`, `requestHeader`, `responseHeader`, `status`, `method`, `userId`) values ('蒋鑫磊', '谭明辉', 'www.micki-dicki.name', '唐雪松', '沈鹏飞', 0, '罗烨华', 27); insert into yuapi.`interface_info` (`name`, `description`, `url`, `requestHeader`, `responseHeader`, `status`, `method`, `userId`) values ('钱雪松', '吕鹏', 'www.andy-russel.org', '范烨伟', '邵黎昕', 0, '苏笑愚', 5460331959); insert into yuapi.`interface_info` (`name`, `description`, `url`, `requestHeader`, `responseHeader`, `status`, `method`, `userId`) values ('严晟睿', '唐晟睿', 'www.nadine-bradtke.name', '郑峻熙', '冯琪', 0, '秦烨华', 90477376); insert into yuapi.`interface_info` (`name`, `description`, `url`, `requestHeader`, `responseHeader`, `status`, `method`, `userId`) values ('段浩轩', '潘文博', 'www.terrence-konopelski.co', '韩雨泽', '袁志强', 0, '陈博文', 9453614492); insert into yuapi.`interface_info` (`name`, `description`, `url`, `requestHeader`, `responseHeader`, `status`, `method`, `userId`) values ('黎健雄', '龙泽洋', 'www.rodney-douglas.io', '冯晟睿', '韩明哲', 0, '蒋弘文', 70703); insert into yuapi.`interface_info` (`name`, `description`, `url`, `requestHeader`, `responseHeader`, `status`, `method`, `userId`) values ('邵绍齐', '范振家', 'www.wilbur-reinger.name', '邹绍辉', '叶哲瀚', 0, '姚子轩', 24180); insert into yuapi.`interface_info` (`name`, `description`, `url`, `requestHeader`, `responseHeader`, `status`, `method`, `userId`) values ('顾博文', '张瑾瑜', 'www.kittie-kautzer.name', '龙修杰', '万弘文', 0, '潘弘文', 334268466); insert into yuapi.`interface_info` (`name`, `description`, `url`, `requestHeader`, `responseHeader`, `status`, `method`, `userId`) values ('唐明辉', '郝耀杰', 'www.ed-barton.org', '蒋晓啸', '段钰轩', 0, '程明', 6); insert into yuapi.`interface_info` (`name`, `description`, `url`, `requestHeader`, `responseHeader`, `status`, `method`, `userId`) values ('陶泽洋', '龙语堂', 'www.shane-braun.io', '杜驰', '徐笑愚', 0, '熊展鹏', 8); insert into yuapi.`interface_info` (`name`, `description`, `url`, `requestHeader`, `responseHeader`, `status`, `method`, `userId`) values ('孔炎彬', '龚昊天', 'www.rolf-wiegand.net', '赵明辉', '覃昊天', 0, '白天宇', 7453201); insert into yuapi.`interface_info` (`name`, `description`, `url`, `requestHeader`, `responseHeader`, `status`, `method`, `userId`) values ('陈梓晨', '高志泽', 'www.norah-goldner.org', '何鹤轩', '郝鹏', 0, '李煜城', 791); insert into yuapi.`interface_info` (`name`, `description`, `url`, `requestHeader`, `responseHeader`, `status`, `method`, `userId`) values ('白思', '汪懿轩', 'www.hugo-bradtke.co', '于立轩', '毛楷瑞', 0, '罗俊驰', 3219); insert into yuapi.`interface_info` (`name`, `description`, `url`, `requestHeader`, `responseHeader`, `status`, `method`, `userId`) values ('李琪', '谭健雄', 'www.gerry-dicki.biz', '龙果', '吴晟睿', 0, '马昊焱', 61151986); insert into yuapi.`interface_info` (`name`, `description`, `url`, `requestHeader`, `responseHeader`, `status`, `method`, `userId`) values ('吴思', '吴志泽', 'www.kareem-feest.io', '汪黎昕', '赵瑾瑜', 0, '邱致远', 6);
```
#### 2.2 创建接口信息表(时间点 01:39:56-01:42:23)
在`sql目录`新建`db.sql`:
鼠标右键`sql目录`选择`New`。
![[qj4QRBZoZqrsXxGQ-d5bb4214.webp]]
输入文件名,点击[Enter]回车。
![[TqZSSBv9R8wUQxeS-840f21b0.png]]
把刚刚复制的`SQL语句`粘贴至`db.sql`内。
上方会弹出是否使用 Mysql 语法,点击`Use Mysql`,这个文件就会以 Mysql 语法格式显示。
![[HOexYQ2BRYzarJzc-a370f3a2.webp]]
在最上方添加`use yuapi;`语句 — 指定往哪个数据库里插入。
![[NaBqxZrCY1eLsne1-fbe8951f.webp]]
把光标放到文件内,按[Ctrl+A]全选,点击左上方
![[Fw5EG1XJqx3Qwlzj-7cd5b667.png]]绿色运行按钮。
![[cPxpBeHwGj7Anw4f-05865935.webp]]
右侧就多了一个创建好的`interface_info`表。
![[ehvMG6oSTMMm3ihL-7b36ce68.webp]]
点击`interface_info`表,查看表中的内容,此时已经有了现成的模拟数据。
![[558fX5aAGFIHodic-22e79864.webp]]
### 3. mybatisX 插件生成代码(时间点 01:42:23-02:00:48)
表已经有了,现在就可以生成增删改查代码了。
#### 3.1 mybatisX 插件安装
点击左上角菜单栏上的 File → settings → plugins。
![[cv5kKihH3aedVn7d-8cf97cf4.webp]]
在搜索框输入 MyBatisX,点击回车(步骤如下图所示)。
![[tFBp3KdN915PWbqx-562a62e7.webp]]
#### 3.2 mybatisX 插件使用**(时间点 01:42:52-01:43:42)**
右键`interface_info`表,选择`MybatisX-Generator`。
![[tD2gm2atvb08FDVN-b8deca26.webp]]
模块路径选择当前路径即可。
![[SvjF4JWEBGWQUk3t-8070cd76.webp]]
点击`Next`。
![[O4vs0aNFxbus252m-62bcd9b1.webp]]
如果发现点击`Next`出现的还是和上图一样,就点击`Previous`(往前一步),再点击`Next`(下一步)。
ps.这个插件有一点bug ╮(╯▽╰)╭
如下图所示选择后,点击`Finish`。
![[OheWtapX67Fw1QxA-08a1ddd6.webp]]
多了一个`generator目录`,代码都生成在这里。
![[Fq67VTRMVYSdte5s-486aa9e4.png]]
#### 3.3 迁移生成的代码(时间点 01:43:42-02:00:48)
复制`generator目录`下的`service目录`;
粘贴至`com.yupi.project`下。
![[9kqW3PGKpJJkMk9m-dea170af.webp]]
复制`generator目录`→ `domain目录`下的`InterfaceInfo`;
粘贴至`com.yupi.project` → `model目录` → `entity目录`下。
![[p7gHsafmyJajqUrw-706bd710.webp]]
复制`generator目录`→ `mapper目录`下的`InterfaceInfoMapper`;
粘贴至`com.yupi.project` → `mapper目录`下;
然后把`generator目录`删掉。
![[cwQIr6YzWFcdMcXy-663ba26b.webp]]
底下的`InterfaceInfoMapper.xml`是`mybatis-plus`用到的一个配置文件,会和`mapper`文件关联上。
它现在生成的包名是`generator目录`的,我们已经移到别的目录下了。
![[r9WLQZDJjFsiokPN-f3549a51.webp]]
选中`generator.mapper`,按住[Ctrl+Shift+R]]全局搜索。
![[I4t70OmHigoSnAZF-fa5b8f4f.webp]]
`InterfaceInfoMapper`也是引用了`generator目录`,我们要修改为正确的路径。
![[ETzIGktmm69497ES-7851b39d.webp]]
选中`generator.domain`,按住[Ctrl+Shift+R]全局搜索。
![[oZqAt10XtvZIqNE0-0c64a5b9.webp]]
现在 mapper、service 层有了,还缺一个 controller 层。
来到`PostController`,按[Ctrl+Alt+O]自动删除未使用的导入语句,并将重复的导入语句合并成一个。
![[kJhYAUWmDTMTGWVP-77c5a953.webp]]
初始化项目模板里有现成的增删改查、分页查询,可以直接用,复制`PostController`粘贴`controller目录`下,并重命名为`InterfaceInfoController`(接口信息
Controller)。
![[tKScWNq7AITEtpow-919f65da.webp]]
`InterfaceInfoController`内选中`post`按住`Ctrl+R`;
将`post`全部替换成`interfaceInfo`。
![[x1Y78vFvjRmgzJsj-8022f48b.webp]]
把`Post`全部替换成`InterfaceInfo`。
![[JtKfpvoQyIp6gxX9-a8ddc431.webp]]
将`InterfaceInfoMapping`全部替换成`PostMapping`,因为`PostMapping`是
springMVC 的语法。
![[aOHOQ8U1bEiWB6gN-38948cd7.webp]]
这里我们要自己编写这个用户要传递的参数的对象。
![[KOZnpfXMopMUgbIy-0614cea3.webp]]
![[scA10dIszZZ5VphU-5c796277.webp]]
所以说现在我们要自己去复制粘贴一下,我们要自己写一下这个参数;
在`model目录`→`dto目录`下创建`interfaceInfo目录`。
![[PsjO5kAVLeUyH6Nk-12496d72.webp]]
复制`post目录`下的文件;`PostDoThumbRequest`是之前点赞的一个请求参数,不用复制。
![[oYKwiCzwoXQHhNHA-84710eb0.webp]]
把文件名中的`Post`改为`InterfaceInfo`。
![[qgrNJRC7hnGSR4LL-f5ad413e.webp]]
我们要自己编写这个用户要传递的参数的对象,而这个对象就是用来接收前端的请求参数,为了更好地满足不同表的设计,我们应该选择用户需要输入哪些字段;
---
💭 你可能会问,**为什么要将其分成三个接口?** 如果你们还在学校,可能觉得一个接口就能解决问题,但实际上,你们需要考虑写接口的目的
—— 是为了服务前端开发人员。
想象一下,如果你只用一个通用对象处理所有事情,当前端开发人员查看你的接口文档时,他们应该如何知道哪些参数是必需的,哪些参数是可选的?你可能需要额外添加备注来说明这一点。而且,如果有些参数并不需要前端提交,如userid、id等,你还需要把它们包含在文档中吗?这可能会造成前端开发人员的困扰。
另一个问题是,如果你在对象中公开了一些字段,而开发者又没有留意到,就可能导致问题。例如,如果你公开了 userid,而且在编码时没有注意,如果用户提交了一个
userid,然后这个 userid 被无意中写入数据库,导致用户控制了这个 userid,这将是一个问题。
如果你觉得写三个对象很麻烦,那你可以按照自己的开发习惯来。然而,从规范的角度出发,建议为每个接口创建一个单独的对象,除非这个对象是特别通用的。例如,对于只需要一个
ID 参数的删除请求,你可以将其转换为通用的。总的来说,通用的时候就使用通用对象,不通用的时候就不要强行通用。
---
找到`entity目录`下的`InterfaceInfo`。
![[5igDG3pIWe6lf95U-2a622ca9.webp]]
在`isDelete`字段上添加`@TableLogic`;
因为是逻辑删除,要添加`@TableLogic`注解,不然就被真实的删除了。
![[pxxHX1h4H2IvFCFh-10561226.webp]]
复制以下`InterfaceInfo`的字段。
```java
/** * 主键 */ @TableId(type = IdType.AUTO) private Long id; /** * 名称 */ private String name; /** * 描述 */ private String description; /** * 接口地址 */ private String url; /** * 请求头 */ private String requestHeader; /** * 响应头 */ private String responseHeader; /** * 接口状态(0-关闭,1-开启) */ private Integer status; /** * 请求类型 */ private String method; /** * 创建人 */ private Long userId; /** * 创建时间 */ private Date createTime; /** * 更新时间 */ private Date updateTime; /** * 是否删除(0-未删, 1-已删) */ @TableLogic private Integer isDelete;
```
替换掉`InterfaceInfoAddRequest`的字段。
```java
package com.yupi.project.model.dto.interfaceInfo; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableLogic; import lombok.Data; import java.io.Serializable; import java.util.Date; /** * 创建请求 * * @TableName product */ @Data public class InterfaceInfoAddRequest implements Serializable { /** * 主键; * 在用户上传接口的时候,不用自己输入 id,后台会自动生成,所以不用用户上传;(刪掉) */ // @TableId(type = IdType.AUTO) // private Long id; /** * 名称 */ private String name; /** * 描述 */ private String description; /** * 接口地址 */ private String url; /** * 请求头 */ private String requestHeader; /** * 响应头 */ private String responseHeader; /** * 接口状态(0-关闭,1-开启) * 接口状态默认都是关闭,有默认值就不用用户上传;(刪掉) */ // private Integer status; /** * 请求类型 */ private String method; /** * 创建人 * 创建人由当前登录的用户自动同步到后台,是后台自动生成的,不用用户上传;(刪掉) */ // private Long userId; // 创建时间、更新时间、是否删除都是自动生成的,不用用户上传;(刪掉) /** * 创建时间 */ // private Date createTime; /** * 更新时间 */ // private Date updateTime; /** * 是否删除(0-未删, 1-已删) */ // @TableLogic // private Integer isDelete; } package com.yupi.project.model.dto.interfaceInfo; import lombok.Data; import java.io.Serializable; /** * 创建请求 * * @TableName product */ @Data public class InterfaceInfoAddRequest implements Serializable { /** * 名称 */ private String name; /** * 描述 */ private String description; /** * 接口地址 */ private String url; /** * 请求头 */ private String requestHeader; /** * 响应头 */ private String responseHeader; /** * 请求类型 */ private String method; }
```
再来编写查询对象,把复制的`InterfaceInfo`的字段,替换掉`InterfaceInfoQueryRequest`的字段。
```java
package com.yupi.project.model.dto.interfaceInfo; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableLogic; import com.yupi.project.common.PageRequest; import lombok.Data; import lombok.EqualsAndHashCode; import java.io.Serializable; import java.util.Date; /** * 查询请求 * * @author yupi */ @EqualsAndHashCode(callSuper = true) @Data public class InterfaceInfoQueryRequest extends PageRequest implements Serializable { /** * 主键 * 用户可能根据id查询 */ // @TableId(type = IdType.AUTO) private Long id; /** * 名称 * 用户可能根据名称查询 */ private String name; /** * 描述 * 用户可能根据描述查询 */ private String description; /** * 接口地址 * 用户可能根据地址查询 */ private String url; //下面的都有可能 /** * 请求头 */ private String requestHeader; /** * 响应头 */ private String responseHeader; /** * 接口状态(0-关闭,1-开启) */ private Integer status; /** * 请求类型 */ private String method; /** * 创建人 */ private Long userId; // 用户不太可能根据创建时间、更新时间、是否删除查询,一般都是范围查询(删除) /** * 创建时间 */ // private Date createTime; /** * 更新时间 */ // private Date updateTime; /** * 是否删除(0-未删, 1-已删) */ // @TableLogic // private Integer isDelete; } package com.yupi.project.model.dto.interfaceInfo; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableLogic; import com.yupi.project.common.PageRequest; import lombok.Data; import lombok.EqualsAndHashCode; import java.io.Serializable; import java.util.Date; /** * 查询请求 * * @author yupi */ @EqualsAndHashCode(callSuper = true) @Data public class InterfaceInfoQueryRequest extends PageRequest implements Serializable { /** * 主键 */ private Long id; /** * 名称 */ private String name; /** * 描述 */ private String description; /** * 接口地址 */ private String url; /** * 请求头 */ private String requestHeader; /** * 响应头 */ private String responseHeader; /** * 接口状态(0-关闭,1-开启) */ private Integer status; /** * 请求类型 */ private String method; /** * 创建人 */ private Long userId; }
```
`InterfaceInfoUpdateRequest`(更新请求) —— 哪些字段的值是可以交给用户或管理员整改的。
```java
package com.yupi.project.model.dto.interfaceInfo; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableLogic; import lombok.Data; import java.io.Serializable; import java.util.Date; /** * 更新请求 * * @TableName product */ @Data public class InterfaceInfoUpdateRequest implements Serializable { /** * 主键 * id不能改,但是我们更新对象需要知道是更新哪条数据 */ // @TableId(type = IdType.AUTO) private Long id; // 下面的都有可能会改 /** * 名称 */ private String name; /** * 描述 */ private String description; /** * 接口地址 */ private String url; /** * 请求头 */ private String requestHeader; /** * 响应头 */ private String responseHeader; /** * 接口状态(0-关闭,1-开启) */ private Integer status; /** * 请求类型 */ private String method; /** * 创建人 * 创建人一般情况下不会改(删除) * 如果你限定只有管理员能改,修改创建人也是合理的 */ // private Long userId; // 创建时间、更新时间、是否删除也不能改 /** * 创建时间 */ // private Date createTime; /** * 更新时间 */ // private Date updateTime; /** * 是否删除(0-未删, 1-已删) */ // @TableLogic // private Integer isDelete; } package com.yupi.project.model.dto.interfaceInfo; import lombok.Data; import java.io.Serializable; /** * 更新请求 * * @TableName product */ @Data public class InterfaceInfoUpdateRequest implements Serializable { /** * 主键 */ private Long id; /** * 名称 */ private String name; /** * 描述 */ private String description; /** * 接口地址 */ private String url; /** * 请求头 */ private String requestHeader; /** * 响应头 */ private String responseHeader; /** * 接口状态(0-关闭,1-开启) */ private Integer status; /** * 请求类型 */ private String method; }
```
现在我们用来接收前端请求参数的对象全部写好了,回到`InterfaceInfoController`,引入这三个请求。
![[tUIo8Fy8aL5iXvh4-27f6a900.webp]]
还有几个爆红的地方,如`validInterfaceInfo`(参数校验);
由于每个不同的表或对象需要校验的参数肯定是各不相同的,所以我们需要自己编写参数校验的代码。
![[KHLtOJkncfkpRF7v-fb664f96.webp]]
具体来说,我们需要确定如何对这些接口信息进行参数校验;
复制`PostServiceImpl`的校验逻辑,粘贴到`InterfaceInfoServiceImpl`中。
![[szdk8CSbCrFEHT44-90f9d3f3.webp]]
选中`InterfaceInfoServiceImpl`中的`Post`,然后按住[Ctrl+R]。
![[U703NxHAXXSh2YjM-53684b96.webp]]
把`post`替换成`interfaceInfo`。
![[e5c65N55uHKgzDUx-8ea90f2d.webp]]
复制接口。
![[rYErgHELfsCKOYic-eebee02b.webp]]
粘贴到`InterfaceInfoService`。
![[VrclSetOTNy3KuoN-0ef312f8.webp]]
接下来就要编写下图所示校验接口信息的方法。
![[rF8n2JlCO87epNXK-2ca1115d.webp]]
先删除`InterfaceInfoServiceImpl`多余的内容。
![[kRXT7WSRS7SUBzu6-dc44f7f7.webp]]
```java
package com.yupi.project.service.impl; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.yupi.project.common.ErrorCode; import com.yupi.project.exception.BusinessException; import com.yupi.project.model.entity.InterfaceInfo; import com.yupi.project.service.InterfaceInfoService; import com.yupi.project.mapper.InterfaceInfoMapper; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; /** * @author Administrator * @description 针对表【interface_info(接口信息)】的数据库操作Service实现 * @createDate 2023-06-04 21:40:56 */ @Service public class InterfaceInfoServiceImpl extends ServiceImpl implements InterfaceInfoService { @Override public void validInterfaceInfo(InterfaceInfo interfaceInfo, boolean add) { if (interfaceInfo == null) { throw new BusinessException(ErrorCode.PARAMS_ERROR); } // 创建时,所有参数必须非空 if (add) { if (StringUtils.isAnyBlank()) { throw new BusinessException(ErrorCode.PARAMS_ERROR); } } if (StringUtils.isNotBlank(content) && content.length() > 8192) { throw new BusinessException(ErrorCode.PARAMS_ERROR, "内容过长"); } } }
```
在这里推荐安装一个插件,点击左上角 File → Settings → Plugins , 搜索`GenerateAllSetter`。
![[b5w1hva4Pr6uG4os-4f5baaaa.webp]]
ps.这个插件可以一键帮你生成对象的所有 get、set 方法、生成一个充满假数据的类,充满假数据的对象等等。
把光标放在`interfaceInfo`上,按[Alt+Enter],选择第二个,调用该对象所有的 get 方法。
![[2wvrxhnRTOYcgUKo-c0d6068d.webp]]
瞬间生成 get 方法。
![[slnYlLOq4N4tSNd3-5c9dd4da.webp]]
这里我们就只拿接口名称进行校验测试,就假设用户第一次创建对象时,他必须传接口名称。
```java
package com.yupi.project.service.impl; import java.util.Date; import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.yupi.project.common.ErrorCode; import com.yupi.project.exception.BusinessException; import com.yupi.project.model.entity.InterfaceInfo; import com.yupi.project.service.InterfaceInfoService; import com.yupi.project.mapper.InterfaceInfoMapper; import org.apache.commons.lang3.StringUtils; import org.springframework.stereotype.Service; /** * @author Administrator * @description 针对表【interface_info(接口信息)】的数据库操作Service实现 * @createDate 2023-06-04 21:40:56 */ @Service public class InterfaceInfoServiceImpl extends ServiceImpl implements InterfaceInfoService { @Override public void validInterfaceInfo(InterfaceInfo interfaceInfo, boolean add) { // 判断接口信息对象是否为空,为空则抛出参数错误的异常 if (interfaceInfo == null) { throw new BusinessException(ErrorCode.PARAMS_ERROR); } // 获取接口信息对象的名称 String name = interfaceInfo.getName(); // 如果是添加操作,所有参数必须非空,否则抛出参数错误的异常 if (add) { if (StringUtils.isAnyBlank(name)) { throw new BusinessException(ErrorCode.PARAMS_ERROR); } } // 如果接口名称不为空且长度大于50,抛出参数错误的异常,错误信息为"名称过长" // 本期写成<50,(没有解决),第二期视频中解决了 if (StringUtils.isNotBlank(name) && name.length() > 50) { throw new BusinessException(ErrorCode.PARAMS_ERROR, "名称过长"); } } }
```
校验搞定,回到`InterfaceInfoController`,还有几个报错的地方`Content`(模糊搜索),改成`description`(描述)。
![[6JPQ6LiPyRzvyqZ1-9264870e.webp]]
重启后端项目,访问 [http://localhost:7529/api/doc.html,打开接口文档。](http://localhost:7529/api/doc.html%EF%BC%8C%E6%89%93%E5%BC%80%E6%8E%A5%E5%8F%A3%E6%96%87%E6%A1%A3%E3%80%82)
![[kMZwuys8eU3M9Vju-f3037983.webp]]
增加接口信息、删除接口信息、根据id获取接口信息.... 这些都生成好了。
后端搞定🥳
## 十五、前端开发(时间点 02:00:48-02:49:32)
现在我们的前端需要做哪些事情呢?是不是要去实现用户登录?实现接口信息的增删改查?
### 1. 前端模板优化(时间点 02:01:13-02:02:36)
#### 1.1 部分前端目录介绍
![[bhB6I3VIbL1frhtU-2112c098.webp]]
| **目录** | **介绍** |
| --- | --- |
| ![[fkSyneA5zebtCMzM-11ee6c1d.png]] | 框架自动生成的隐藏文件(不用管) |
| ![[Zj9obHYtlF8ZjIPJ-46412c43.png]] | 业务通用组件 |
| ![[HvFkd408MD1jnHU3-cd44e8d7.png]] | 业务页面入口和常用模板 |
| ![[QmPpZ270p5UokGvf-2735357b.png]] | 后台接口服务 |
| ![[5cA6ySc5IGvihJTN-faa8ba42.png]] | 整个 Ant Design Pro 框架的主要文件 |
| ![[R2YaVDNBV4miLYGE-4a90b4fe.png]] | 保证前端项目代码规范。在 webstorm 中可以按`Ctrl+Alt+L`格式化代码,可以让代码看起来更美观。本地还没有去引用这个美化配置。(详细步骤跳至标题**启用美化配置**) |
app.ts 是 Ant Design Pro 的核心入口文件,其中的 getInitialState 函数用于在我们首次打开页面时加载某些用户信息。例如,用户信息等。除非是登录页面,否则它会自动跳转,这都是由
Ant Design Pro 预先编写的逻辑,我们无需过分关注。
#### 1.2 启用美化配置
这里用`webstorm`作示范,`vscode`的,懒的配置,咳咳(绝不是找不到,别打别打🐶)
点击 File → Settings。
![[VgQ72PvCnKZKDOdn-4938d548.webp]]
搜索`eslint`。
![[UanVismR4iKdNoXc-46191a3e.webp]]
搜索`prettier`。
![[6nsxW1trLfT46CuQ-4d57c52a.webp]]
这样的话,写代码的时候就会自动格式化了。
### 2. 前端代码自动生成(时间点 02:02:36-02:49:32)
#### 2.1 介绍(时间点 02:03:06-02:07:20)
我们现在需要解决的问题是如何让前端调用后台接口,实现接口信息的增删改查管理。通常,我们会在前端定义 TS 类型对象,并手动编写调用后台的方法,例如获取当前登录用户、退出登录等。写前端代码的同学们,你们是自己编写调用后台接口的代码还是利用了某种自动生成方法?相信一些同学已经尝试过使用自动生成方法。我们正在使用的
Ant Design Pro 框架,它已经支持自动化生成这些接口。现在,向大家介绍一个叫做 openapi 的插件,它可以帮助我们实现接口的自动生成。
接下来,我们来探讨如何实现接口的自动生成。如果后端已经定义了各种接口,我们如何自动生成相应的接口调用代码呢?我们需要一个文档或者介质来同步这些信息。这就是我们需要使用
openapi 规范的地方。只要为 oneapi 插件提供基于其规范的文档,就可以实现接口的自动生成。
那么,什么是 oneapi 呢?简单地说,oneapi 是一种接口文档的规范,可以理解为接口文档的格式或者规则。举个例子,我们常用的 Swagger
这种后端接口文档,就是遵循了 openapi 规范。
访问 [http://localhost:7529/api/doc.html,查看接口文档的主页。](http://localhost:7529/api/doc.html%EF%BC%8C%E6%9F%A5%E7%9C%8B%E6%8E%A5%E5%8F%A3%E6%96%87%E6%A1%A3%E7%9A%84%E4%B8%BB%E9%A1%B5%E3%80%82)
![[RiBXg4EvBECgmlQm-cded085d.webp]]
这里有个`v3/api-docs`,访问 [http://localhost:7529/api/v3/api-docs,这里能看见一堆](http://localhost:7529/api/v3/api-docs%EF%BC%8C%E8%BF%99%E9%87%8C%E8%83%BD%E7%9C%8B%E8%A7%81%E4%B8%80%E5%A0%86) Json
的代码,这个东西就是遵循 openapi 的规范提供的 Json 的文档,第一行信息说明它是基于 openapi3 版本去生成的文档 。
![[LiJli8UHdP1ofAoN-aba84be8.webp]]
所以我们如果现在已经有了现成的后端,想要前端直接生成调用这些后端的代码,只要把这个基于这个 openapi 规范的文档提供给前端就可以了。
#### 2.2 前端代码自动生成(时间点 02:07:20-02:11:30)
复制 [http://localhost:7529/api/v3/api-docs,回到前端,找到`config.ts`中的`openAPI`插件。](http://localhost:7529/api/v3/api-docs%EF%BC%8C%E5%9B%9E%E5%88%B0%E5%89%8D%E7%AB%AF%EF%BC%8C%E6%89%BE%E5%88%B0%60config.ts%60%E4%B8%AD%E7%9A%84%60openAPI%60%E6%8F%92%E4%BB%B6%E3%80%82)
![[xKsSteojcJj7fv2w-ea9055a1.webp]]
把多余的删掉。
![[aOUNOv1lFcnUtl4r-4b42549f.webp]]
修改内容。
![[XW9qMqO5Wf8uDFes-01e5409c.webp]]
打开`package.json`,找到`openapi`运行(或在终端输入`yarn run openapi`)。
![[tACdi8XuN3obQhVd-66aed70a.webp]]
提示成功生成 service 文件。
![[rLcp4NBSZTiN9D20-a9eeda97.webp]]
在`services目录`多出一个`yuapi-backend目录`,里面自动生成调用后端指定接口的方法代码。
![[3VqrpUIWHeKtj5gb-4fdf4b0d.webp]]
可以点击`interfaceInfoController.ts`查看,生成了后端接口方法全部的代码。
![[9OREGHG9GOdjX87e-13f6edf4.webp]]
![[AvXRzeWP8E4szOsL-023c88d0.webp]]
而且还生成了 ts 类型,比如选中`InterfaceInfoAddRequest`(新增接口信息),然后按[Ctrl+鼠标左键]。
![[kL3gBfkI27AmMFUR-b0800049.webp]]
就可以看见它的请求参数类型:name 名称、description 描述、url 接口地址、requestHeader 请求头、responseHeader
响应头、method 请求类型。
![[dmS2Tor0ZN8gCUsi-0c683713.webp]]
这个和我们在后端写的`InterfaceInfoAddRequest`(创建请求)完全一致:
![[zdgHYr7EqyztnW8d-913f019c.webp]]
ps.所以,当有同学问为什么每个请求参数都需要单独编写一个方法,单独创建一个对象时,现在你应该明白了原因。是的,这样做可以根据这些对象直接生成清晰、易于使用的前端文档。这是不是非常便捷呢?
#### 2.3 修改请求配置(时间点 02:11:30-02:12:42)
这里的请求配置(requestErrorConfig.ts)写的不是很好(个人感觉🐶)。
![[9Pt5lPnVZ3g0haLe-64e46108.webp]]
重命名为`requestConfig.ts`。
![[wFKqABckfaemOXHO-84f8ab66.webp]]
回到`requestConfig.ts`。
![[Asl2jPV57Tb6pDi5-96cc3e9d.webp]]
把`errorConfig`改为`requestConfig`。
![[mA1QmRb8ecZXwBgL-e88d4d70.webp]]
在`app.tsx`里引入修改了的请求配置。
![[trCnB4XgDUYy6pdl-16453801.webp]]
虽然模板(requestConfig.ts)生成的代码原本只期望你在这里处理错误,但是在`app.tsx`中又完全引用了这个对象,我们直接将所有的请求配置都写在一个对象中,不需要在`app.tsx`中再嵌套一个对象。
在`requestConfig.ts`编写前端的请求地址为后端地址`http://localhost:7529`。
![[q9DNFMCdUJCqPOZ9-163b8275.webp]]
#### 2.4 前后端对接(时间点 02:12:42-02:27:00)
我们要请求真实的后端接口,用`dev模式`(或在前端终端输入`yarn run dev`)去启动项目,访问
[http://localhost:8000/;](http://localhost:8000/%EF%BC%9B)
登录后发现登录失败,鼠标右键 → 选择`检查` → 点击`网络`,查看一下登录请求。
![[vuzlzut14phjfkUx-6605e99c.webp]]
状态码为 404,因为它请求的接口地址不对,这个接口请求的接口地址是脚手架自动生成的。
![[ytxOVRnEcvgJmYr1-3f94e174.webp]]
所以要改成自己的后端,去找到登录的`index.tsx`, 当用户点击登录按钮之后,会执行`handleSubmit`提交函数。
![[wQLwMBmL4Bljz0HA-3a9c2b81.webp]]
这里实际上是请求咱们的后端,要改成刚刚用 openapi 生成的调用后端的方法。
![[tVQBbh9DVZPc8wnx-fa5da2af.webp]]
![[gGX66CzAIXQYUJJd-913d50c0.webp]]
为什么有现成的方法?因为我们用 openapi 自动生成了(如图所示):
![[94Bya8OisgoIiS7g-9728d1ba.webp]]
所以这里就不用再去写方法了,直接改成参数即可;
把`type`删掉,这里`values`的类型本来是 Ant Design Pro 框架生成的类型,改成自己的后端类型。
![[Wu3kzqGELnfMMw19-5f0a6763.webp]]
修改响应判断。
![[UPMQ68uaf7S4xu8K-348aed14.webp]]
修改后:
![[Hsk29VFtkQqeDQpO-cc31c886.webp]]
```java
const handleSubmit = async (values: API.UserLoginRequest) => { try { // 调用 userLoginUsingPOST 方法进行用户登录,values 为包含登录信息(如用户名和密码)的对象 const res = await userLoginUsingPOST({ ...values, }); // 检查返回的 res 对象中是否包含 data 属性,如果包含则表示登录成功 if (res.data) { // 创建一个新的 URL 对象,并获取当前 window.location.href 的查询参数 const urlParams = new URL(window.location.href).searchParams; // 将用户重定向到 'redirect' 参数指定的 URL,如果 'redirect' 参数不存在,则重定向到首页 ('/') history.push(urlParams.get('redirect') || '/'); // 用登录用户的数据更新初始状态 setInitialState({ loginUser: res.data }); return; } // 如果抛出异常 } catch (error) { // 定义默认的登录失败消息 const defaultLoginFailureMessage = '登录失败,请重试!'; // 在控制台打印出错误 console.log(error); // 使用 message 组件显示错误信息 message.error(defaultLoginFailureMessage); } };
```
然后把它给我们这个页面生成的一些类型都改为自己的。
![[EgvTG4YHgRn8hdZz-6ffb7beb.webp]]
把`LoginParams`改为`UserLoginRequest`。
![[kgj9llOPEEpBPHys-d45c4168.webp]]
把表单用户名和密码的`name`改成后端的字段:userAccount、userPassword;
这样的话,用户在表单输入的值才会真正传递给后台。
![[GGTTNuqmaMdUrKQR-f0804ef8.webp]]
ps.通常,前端从后台加载用户信息后,会将数据保存到全局状态中。当现在并未保存到全局状态,所以要进行相应的修改。找到`app.tsx`,这里有一个 `getInitialState` 的方法。每当首次访问页面时,就会被执行用以获取用户信息和当前的全局状态。
找到`typings.d.ts`,定义全局状态类型(InitialState),保存用户登录的状态(当成全局变量即可)。
![[fZ2KxSMPAfLhc4wz-4256a226.webp]]
找到`app.tsx`(全局入口文件)。
![[faeNURtWRWW11pDf-627cd313.webp]]
把上图的对象改成我们刚刚设置的类型。
![[CW6yfIFXe4RYeI6r-6a623009.webp]]
我们把状态改了之后,这里有一个函数(如下图所示)。这个函数做什么事情呢?
当页面首次加载时,获取要全局保存的数据,比如用户登录信息,这里默认生成的代码就有一个`fetchUserInfo`(获取当前用户信息)。
![[ShnXwb4gfXxHyFWn-025d3f2c.webp]]
把`queryCurrentUser`改成自己的接口`getLoginUserUsingGET`(也是
openapi 自动生成),也不用传任何参数:
![[mZpHOva3NzPLfumE-8e0d0cf5.webp]]
接下来要把状态保存至全局状态中,先定义一个全局状态。
![[XAaxabdcmCG3YL89-86b20b87.webp]]
删除多余的内容。
![[rVfxRt1CRLuXPCqg-26cf181b.webp]]
```javascript
export async function getInitialState(): Promise { // 当页面首次加载时,获取要全局保存的数据,比如用户登录信息 const state: InitialState = { // 初始化登录用户的状态,初始值设为undefined loginUser: undefined, } try { // 调用getLoginUserUsingGET()函数,尝试获取当前已经登录的用户信息 const res = await getLoginUserUsingGET(); // 如果从后端获取的数据不为空,就把获取到的用户数据赋值给state.loginUser if (res.data) { state.loginUser = res.data; } // 如果在获取用户信息的过程中发生错误,就把页面重定向到登录页面 } catch (error) { history.push(loginPath); } // 返回修改后的状态 return state; };
```
往下滑,把`currentUser`改成`loginUser`,把`name`改成`userName`,`avatar`改成`userAvatar`。
![[Y0LNXIOd9026Pisd-b0d78792.webp]]
以`dev模式`启动前端,访问 [http://localhost:8000/,注册可以去用户中心复制一个进行改造,这里之前在后端接口文档注册过一个账号。](http://localhost:8000/%EF%BC%8C%E6%B3%A8%E5%86%8C%E5%8F%AF%E4%BB%A5%E5%8E%BB%E7%94%A8%E6%88%B7%E4%B8%AD%E5%BF%83%E5%A4%8D%E5%88%B6%E4%B8%80%E4%B8%AA%E8%BF%9B%E8%A1%8C%E6%94%B9%E9%80%A0%EF%BC%8C%E8%BF%99%E9%87%8C%E4%B9%8B%E5%89%8D%E5%9C%A8%E5%90%8E%E7%AB%AF%E6%8E%A5%E5%8F%A3%E6%96%87%E6%A1%A3%E6%B3%A8%E5%86%8C%E8%BF%87%E4%B8%80%E4%B8%AA%E8%B4%A6%E5%8F%B7%E3%80%82)
![[Vf8oE7VCYRQExild-002e3ae9.webp]]
我们就用它进行登录。
![[uBfpupXTUEoQfEsh-b2727362.webp]]
登录成功。
![[pivUhtYgqpepsCS3-973682e7.webp]]
左侧菜单栏没有管理员页面,因为我们不是管理员,所以少了一个页面。
##### 问题:需要点击两次登录
此时你突然发现,诶,得点击两次登录按钮,才能跳转到登录之后的页面,还没有报错。
ps.好家伙.jpg 不做了不做了,这项目不做也罢,咳咳,别打别打🐶
这个问题是由于 React 组件更新的异步性质引起的。在调用 setInitialState 后,状态可能并没有立即更新,而你又立即执行了
history.push,试图导航到一个依赖于新状态的页面。这就导致了你需要点击两次登录按钮才能看到预期的页面。
![[5bRb5I1MCzNojOfe-322670f5.webp]]
**为了避免这个问题,可以尝试以下两种方式:**
1. 确保 setInitialState 在 history.push 之前完成: 你可以尝试使用 async/await 来确保 setInitialState
在 history.push 之前完成。在 index.tsx 中的 handleSubmit 修改为以下形式:
```java
const handleSubmit = async (values: API.UserLoginRequest) => { try { const res = await userLoginUsingPOST({ ...values, }); if (res.data) { const urlParams = new URL(window.location.href).searchParams; await setInitialState({ loginUser: res.data }); history.push(urlParams.get('redirect') || '/'); return; } } catch (error) { const defaultLoginFailureMessage = '登录失败,请重试!'; console.log(error); message.error(defaultLoginFailureMessage); } };
```
这个方法进入登录页之后得等上 5 秒左右,然后才能点击登录,点击一次就可以;
如果我们访问后一出现登录页就点击登录,就还需要点击两次登录,好像才解决一半问题诶;
这里是因为 setInitialState 函数的执行需要等待一段时间,或者函数内部有某些延迟操作,导致 setInitialState 函数并没有立即将状态更新,直接加个
setTimeOut 延迟 100ms,确保 setInitialState 在 history.push 之前完成。
```java
const handleSubmit = async (values: API.UserLoginRequest) => { try { // 登录 const res = await userLoginUsingPOST({ ...values, }); // 如果登录成功(响应有数据) if (res.data) { // 获取当前URL的查询参数 const urlParams = new URL(window.location.href).searchParams; // 设置一个延迟100毫秒的定时器 // 定时器触发后,导航到重定向URL,如果没有重定向URL,则导航到根路径 setTimeout(() => { history.push(urlParams.get('redirect') || '/'); }, 100); // 更新全局状态,设置登录用户的信息 setInitialState({ loginUser: res.data }); return; } // 如果抛出异常 } catch (error) { // 定义默认的登录失败消息 const defaultLoginFailureMessage = '登录失败,请重试!'; // 在控制台打印出错误 console.log(error); // 使用 message 组件显示错误信息 message.error(defaultLoginFailureMessage); } };
```
1. 来自球友@YukeSeko 👉 [登录页需要点击两次登录才能进入的问题]
#### 2.5 登录状态失效(时间点 02:27:00-02:28:57)
登录后,刷新页面(按 F5),又要重新登录,说明前端没有 cookie,登录状态失效了;
有些同学可能会纠结于如何保存这个状态,过程是这样的:
当你成功登录后,后端会在前端设置一个 cookie。设立 cookie 后,每当前端页面刷新时,它会执行什么操作呢?大家还记得之前提到的 app.tsx
文件吗?每次页面首次加载时,它都会执行 getInitialState 函数。所以,当我们首次加载页面的时候,这个方法就会被调用以获取当前登录用户的信息。获取到信息后,我们只需将其设置到全局变量中,初始化变量即可。
在`requestConfig.ts`中添加上`withCredentials: true`。
![[6XtnygehvjqDcSST-007d4c13.webp]]
再次登录,然后刷新页面,没有跳转回登录页面,成功。
![[OrR3pzGGAFH7Wo29-973682e7.webp]]
#### 2.6 头像加载
之前登录之后会发现右上角的头像一直在加载状态,这里使用编程导航头像,将链接粘贴至后端数据库中。
![[21tDSOQrXoyFj9Gq-a8a2123c.webp]]
```text
https://yupi.icu/logo.png
```
用户名设置为:老鱼皮。
![[bTOAqwecsK5TADtG-8bab73f3.webp]]
在 AvatarDropdown.tsx 按[Ctrl+F]。
![[WUUUd6ACcpfnRqxy-66615b00.webp]]
把所有的 currentUser 换成 loginUser,name 换成 userName。
ps.webstorm按[Ctrl+R]
![[Ela3dX8wRSfkKovj-0a7db3fb.webp]]
#### 2.7 实现注销(时间点 02:28:59-02:31:25)
找到`AvatarDropdown.tsx`。
![[fTyekguKDo6MVshD-4028457a.webp]]
把默认的注销方法(loginOut)改成我们后端自己的方法(userLogoutUsingPOST)。
![[Jw7fYjHOIQueUauV-ccd480af.webp]]
在 userLogoutUsingPOST 后编写跳转回登录页面。
![[Em5Agqoyi36mhlWE-5ea5f65b.webp]]
这样用户点击`退出登录`后,会先执行登出操作,并清除全局状态中的用户信息,然后跳转到登录页面。
把原先的 loginOut 删掉。
![[wbk7j4kBMrL5OhkM-7718b121.webp]]
```javascript
import { userLogoutUsingPOST } from '@/services/yuapi-backend/userController'; import { LogoutOutlined, SettingOutlined, UserOutlined } from '@ant-design/icons'; import { useEmotionCss } from '@ant-design/use-emotion-css'; import { history, useModel } from '@umijs/max'; import { Spin } from 'antd'; import type { MenuInfo } from 'rc-menu/lib/interface'; import React, { useCallback } from 'react'; import { flushSync } from 'react-dom'; import HeaderDropdown from '../HeaderDropdown'; export type GlobalHeaderRightProps = { menu?: boolean; children?: React.ReactNode; }; export const AvatarName = () => { const { initialState } = useModel('@@initialState'); const { loginUser } = initialState || {}; return {loginUser?.userName}; }; export const AvatarDropdown: React.FC = ({ menu, children }) => { const actionClassName = useEmotionCss(({ token }) => { return { display: 'flex', height: '48px', marginLeft: 'auto', overflow: 'hidden', alignItems: 'center', padding: '0 8px', cursor: 'pointer', borderRadius: token.borderRadius, '&:hover': { backgroundColor: token.colorBgTextHover, }, }; }); const { initialState, setInitialState } = useModel('@@initialState'); const onMenuClick = useCallback( (event: MenuInfo) => { const { key } = event; if (key === 'logout') { flushSync(() => { setInitialState((s) => ({ ...s, loginUser: undefined })); }); userLogoutUsingPOST(); const { search, pathname } = window.location; const redirect = pathname + search; history.replace('/user/login', { redirect }); return; } history.push(`/account/${key}`); }, [setInitialState], ); const loading = ( ); if (!initialState) { return loading; } const { loginUser } = initialState; if (!loginUser || !loginUser.userName) { return loading; } const menuItems = [ ...(menu ? [ { key: 'center', icon: , label: '个人中心', }, { key: 'settings', icon: , label: '个人设置', }, { type: 'divider' as const, }, ] : []), { key: 'logout', icon: , label: '退出登录', }, ]; return ( {children} ); };
```
测试一下,前端在终端输入`yarn run dev`,访问 [http://localhost:8000/,登录。](http://localhost:8000/%EF%BC%8C%E7%99%BB%E5%BD%95%E3%80%82)
![[shIZOP6G7gNAl0tL-e92cd69f.webp]]
登录之后点击头像→`退出登录`,退出成功,跳转回登录页。
![[BrparARXd3f1w8ST-5646588c.webp]]
大家有没有觉得现在编写前端代码变得不那么困难?我们似乎没有怎么手写代码,所有与后台的请求接口以及类型代码都是自动生成的,我们只需要在对应的位置去调用这些方法,使用这些函数,利用这些代码即可。
想一想,我们利用这种自动生成代码的方式有什么优势呢?一个最显著的好处是,如果有一天后台接口发生了变化,比如,现在的创建接口,前端的参数对象是
description,假如有一天我把这个 description 改成了descript。那么我们应该如何处理呢?需要在前端再修改所有的代码,再去替换什么东西吗?其实并不必要。你只需要在前端再生成一下接口即可。只需再使用
openapi 生成,它就会自动地根据后台的最新接口文档重新生成services。
因为方法名没有变,只有参数变了,所以基本上不会有什么影响。所以说,自动生成代码的优点在此处就显得非常重要,它实现了同步,同时还降低了沟通成本。当然,如果遇到一些特殊情况,我们可能还需要进行沟通,具体情况具体分析。
#### 2.8 接口管理(时间点 02:31:26-02:49:32)
现在我们要做一个只有管理员可以使用的接口信息管理。
首先,我们要了解前端是怎么区分权限的。刚刚我们登录后,会发现原本有三个菜单栏,但是管理员菜单消失了。那么这个现象是如何发生的呢?原因就在于前端进行了权限校验。
找到`access.ts`,这个是 Ant Design Pro 为我们内置的一套权限管理机制。
![[uqbfD6FfOQ7ddQRr-0fdad30a.webp]]
修改一下,取全局初始化状态(InitialState)的`loginUser`,根据当前登录用户判断它是否有 管理员权限
or 用户权限。
![[6hGKEirAzlrcLZax-eeb01349.webp]]
```javascript
/** * @see https://umijs.org/zh-CN/plugins/plugin-access * */ export default function access(initialState: InitialState | undefined) { const { loginUser } = initialState ?? {}; return { canUser: loginUser, // 如果loginUser存在,并且用户角色为 'admin',说明该用户是管理员 canAdmin: loginUser?.userRole === 'admin', }; }
```
然后访问到后端,把账号角色改为`admin`。
![[x8Lx4eA9evu4egcd-2ba2b4ee.webp]]
回到前端页面,管理员页面出现了。
![[crpkZ5Q3uR35xNBa-236af9fa.webp]]
现在我们要把接口信息填充进来,来改造一下页面。
借鉴一下表格页,看看怎么写的。
![[qh7T1xq4ZRgVfybm-6c07a3cf.webp]]
![[nCmvxGFLjdEPAkr3-0a26c026.webp]]
`webstorm`按 [Ctrl+Shift+ 减号(-)]全局压缩 — 把代码块都折叠起来。
![[JQuVdIFTDtGCEclq-18782a33.webp]]
`vscode`没翻到类似的功能,手动折叠。
![[t0uN8qyAe5r0nbhr-b07da5c3.webp]]
第一个:处理新建按钮的功能。
![[UtoHzpq1Rlwx0ofo-e0570ffa.png]]
比如点击`新建`→ 弹窗,就是`handleAdd`做的事情。
![[YegasXgl14bvPyJv-42591063.webp]]
![[miM2oAiTBgqvpDBm-28853725.png]]
第二个:处理修改数据的功能。 比如点这个数据的修改,它会去处理更新,就是`handleUpdate`做的事情。
![[XpR17AJS7blgs5FL-1677e2f8.png]]
第三个:处理删除数据的功能。
![[rINwnRYKHzwWtB3j-abbdf090.png]]
往下滑,这里就是 react 写页面的地方,非常简单,这个表格是用到了 Ant Design Pro components 的 ProTable
组件。
![[tktZS522SLwL7Qku-b4aa70f6.webp]]
这个组件只要传入一下你的表格要有哪些列,每一列要怎么展示,就能够自动生成数据。所以现在我们要做的事情就是告诉这个组件它有哪些列,就是要改一下
columns,选中 columns,按[Ctrl+鼠标左键]点进来。
![[kHbGzCSBBVo2zsB1-aa114f1b.webp]]
这里的规则名称、描述、服务调用次数、状态....和前端页面的表格页表头一致。
![[6x7WC9utwpVcf1LF-8906a828.webp]]
![[je7Kv50LBMnScmly-170e4610.webp]]
我们现在改成自己的就行了,字段的名称根据接口文档改。
![[ku32gz9mCfuWuznW-1fca6920.webp]]
```typescript
import { addRule, removeRule, rule, updateRule } from '@/services/ant-design-pro/api'; import { PlusOutlined } from '@ant-design/icons'; import type { ActionType, ProColumns, ProDescriptionsItemProps } from '@ant-design/pro-components'; import { FooterToolbar, ModalForm, PageContainer, ProDescriptions, ProFormText, ProFormTextArea, ProTable, } from '@ant-design/pro-components'; import '@umijs/max'; import { Button, Drawer, Input, message } from 'antd'; import React, { useRef, useState } from 'react'; import type { FormValueType } from './components/UpdateForm'; import UpdateForm from './components/UpdateForm'; /** * @en-US Add node * @zh-CN 添加节点 * @param fields */ const handleAdd = async (fields: API.RuleListItem) => { const hide = message.loading('正在添加'); try { await addRule({ ...fields, }); hide(); message.success('Added successfully'); return true; } catch (error) { hide(); message.error('Adding failed, please try again!'); return false; } }; /** * @en-US Update node * @zh-CN 更新节点 * * @param fields */ const handleUpdate = async (fields: FormValueType) => { const hide = message.loading('Configuring'); try { await updateRule({ name: fields.name, desc: fields.desc, key: fields.key, }); hide(); message.success('Configuration is successful'); return true; } catch (error) { hide(); message.error('Configuration failed, please try again!'); return false; } }; /** * Delete node * @zh-CN 删除节点 * * @param selectedRows */ const handleRemove = async (selectedRows: API.RuleListItem[]) => { const hide = message.loading('正在删除'); if (!selectedRows) return true; try { await removeRule({ key: selectedRows.map((row) => row.key), }); hide(); message.success('Deleted successfully and will refresh soon'); return true; } catch (error) { hide(); message.error('Delete failed, please try again'); return false; } }; const TableList: React.FC = () => { /** * @en-US Pop-up window of new window * @zh-CN 新建窗口的弹窗 * */ const [createModalOpen, handleModalOpen] = useState(false); /** * @en-US The pop-up window of the distribution update window * @zh-CN 分布更新窗口的弹窗 * */ const [updateModalOpen, handleUpdateModalOpen] = useState(false); const [showDetail, setShowDetail] = useState(false); const actionRef = useRef(); const [currentRow, setCurrentRow] = useState(); const [selectedRowsState, setSelectedRows] = useState([]); /** * @en-US International configuration * @zh-CN 国际化配置 * */ // 首先把变量的类型改成我们接口的类型 const columns: ProColumns[] = [ { title: 'id', dataIndex: 'id', valueType: 'index', }, { title: '接口名称', //name对应后端的字段名 dataIndex: 'name', // tip不用管,一个规则 // tip: 'The rule name is the unique key', // render不用管,它是说渲染类型,默认我们渲染类型就是text // render: (dom, entity) => { // return ( // { // setCurrentRow(entity); // setShowDetail(true); // }} // > // {dom} // // ); // }, // 展示文本 valueType: 'text' }, { title: '描述', //description对应后端的字段名 dataIndex: 'description', // 展示的文本为富文本编辑器 valueType: 'textarea', }, { title: '请求方法', dataIndex: 'method', // 展示的文本为富文本编辑器 valueType: 'text', }, { title: 'url', dataIndex: 'url', valueType: 'text', }, { title: '请求头', dataIndex: 'requestHeader', valueType: 'textarea', }, { title: '响应头', dataIndex: 'responseHeader', valueType: 'textarea', }, { title: '状态', dataIndex: 'status', hideInForm: true, valueEnum: { 0: { text: '关闭', status: 'Default', }, 1: { text: '开启', status: 'Processing', }, }, }, { title: '创建时间', dataIndex: 'createTime', valueType: 'dateTime', }, { title: '更新时间', dataIndex: 'updateTime', valueType: 'dateTime', }, { title: '操作', dataIndex: 'option', valueType: 'option', render: (_, record) => [ { handleUpdateModalOpen(true); setCurrentRow(record); }} > 配置 , 订阅警报 , ], }, ]; return ( headerTitle={'查询表格'} actionRef={actionRef} rowKey="key" search={{ labelWidth: 120, }} toolBarRender={() => [ , ]} request={rule} columns={columns} rowSelection={{ onChange: (_, selectedRows) => { setSelectedRows(selectedRows); }, }} /> {selectedRowsState?.length > 0 && ( 已选择{' '} {selectedRowsState.length} {' '} 项 服务调用次数总计 {selectedRowsState.reduce((pre, item) => pre + item.callNo!, 0)} 万 } > )} { const success = await handleAdd(value as API.RuleListItem); if (success) { handleModalOpen(false); if (actionRef.current) { actionRef.current.reload(); } } }} > { const success = await handleUpdate(value); if (success) { handleUpdateModalOpen(false); setCurrentRow(undefined); if (actionRef.current) { actionRef.current.reload(); } } }} onCancel={() => { handleUpdateModalOpen(false); if (!showDetail) { setCurrentRow(undefined); } }} updateModalOpen={updateModalOpen} values={currentRow || {}} /> { setCurrentRow(undefined); setShowDetail(false); }} closable={false} > {currentRow?.name && ( column={2} title={currentRow?.name} request={async () => ({ data: currentRow || {}, })} params={{ id: currentRow?.name, }} columns={columns as ProDescriptionsItemProps[]} /> )} ); }; export default TableList;
```
查看前端的表格页,表头已经变了,只不过现在没有数据,我们只要知道这个表格组件什么时候去请求数据就可以了。
![[AmmNphxDCLHCHPj3-bd5f7346.webp]]
回到表格页,这里有个 request 方法,这个地方其实就是发起一个请求。
![[hlTeZ149zspNdvvA-a00560c3.webp]]
我们直接把这个请求函数改成自己的。
![[Cgh6JxKkr3RbdTwZ-1373dd26.webp]]
---
🪔 这个 request 会在什么时候触发请求?
一是当你刚打开页面或刚加载表格时;
二是当你手动点击刷新按钮时;
三是当你点击查询按钮时;
也就是说这个请求函数何时被调用,完全由这个组件来管理。你只需要设定请求函数如何调用,以及应该请求哪个后端接口即可。我们不再需要反复绑定和编写事件处理程序。
---
然后发现 request 报错了,因为它传递的参数不太一致。
![[PmOTfvKy6WLSZWmD-e0d771c6.webp]]
看看前端页面→`表格页`,也报错了,鼠标右键选择`检查`→`网络`,按`F5`刷新,看看有没有拿到数据,拿到了数据。
![[VoXFGg4IkTO9TGqF-9edf97a0.webp]]
---
已经获取到了数据,但是为何会报错呢?大家猜猜看,如果在替换接口后出现错误,可能的原因是什么?如果你遇到这种情况,你会如何进行排查呢?
首先,我们需要确定你的请求参数与 request 的请求参数是否一致。其次,你的响应值是否与 request 的响应值相匹配。因此,建议大家尽量避免完全替换。
---
把光标放在 request 上,按[Ctrl+鼠标左键]。
![[swyZoGPhDRQnSxdH-d5ac5816.webp]]
看一下 request 接收什么参数。
![[yRLOsPwkTrwU4aIe-7420538a.webp]]
复制参数,粘贴到 request 里。
![[I4WM9cDcbppA73GI-a1b359da.webp]]
```typescript
(params: U & { pageSize?: number; current?: number; keyword?: string; }, sort: Record, filter: Record)
```
然后看一下它要求的响应对象是什么,把光标放在 RequestData 上,按[Ctrl+鼠标左键]。
![[PbLBmYt8uicMMD7e-b5276191.webp]]
![[nHptW6eb5qHhfLoL-d8521f0f.webp]]
写一下这个函数再去调用。
![[BRXt4StQ01l2tYKg-924aca28.webp]]
虽然这里有报错,但是这是 ts 一些语法层面的问题,不用管它。
```properties
request={async (params, sort: Record, filter: Record) => { const res = await listInterfaceInfoByPageUsingGET({ ...params }) if (res?.data) { return { data: res?.data.records || [], success: true, total: res.total, } } }}
```
回到前端页面,刷新一下,在表格页成功显示数据。
![[hyOmEfmBmEXg8x4C-6a2ab1a0.webp]]
实际上我们做了哪些工作呢?我们只是使用了 ProTable 表格,定义了表格应该有的列,然后编写了请求方法并修改了请求后台的接口。这些接口并不是我们手动编写的,而是自动生成的。只要大家遵循这个流程,反复尝试几次,实现增删改查和登录等功能只需要几分钟的时间。
结束咯,时刻保持🐟的活跃,起来走动走动,快支棱起来ψ(`∇´)ψ
## 十六、往期项目
[用户中心](https://www.code-nav.cn/course/1790943469757837313):学习完整的项目开发流程
[伙伴匹配系统](https://www.code-nav.cn/course/1790950013153095682):巩固开发流程,redis、
事务、后端开发知识、大数据推荐思想、并发
[API 开放平台](https://www.code-nav.cn/course/1790979723916521474):主打架构设计+
sdk 开发+ API 签名认证+ RPC
[聚合搜索平台](https://www.code-nav.cn/course/1790979621621641217):学习爬虫+
Elastic Stack +业务逻辑
---
**项目分区导航**:⬅️ [[07-API 开放平台项目笔记|07-API 开放平台项目笔记]] | 08-项目笔记(一) | ➡️ [[09-项目笔记(七) - API 开放项目项目教程 - 编程导航教程|09-项目笔记(七) - API 开放项目项目教程 - 编程导航教程]]