--- title: "10-项目笔记(三)" created: 2025-12-02 tags: - 项目 aliases: - 项目笔记(三) --- # 项目笔记(三) ## 一、项目概述 本项目是一个面向开发者的 API 平台,提供 API 接口供开发者调用。用户通过注册登录,可以开通接口调用权限,并可以浏览和调用接口。每次调用都会进行统计,用户可以根据统计数据进行分析和优化。管理员可以发布接口、下线接口、接入接口,并可视化接口的调用情况和数据。本项目侧重于后端,涉及多种编程技巧和架构设计层面的知识。 ## 二、本期时间点 ![[QG60IT2m4PKw8pmu-4946e1e5.webp]] ## 三、本期计划(时间点 04:36-09:07) 1. 开发接口发布 / 下线的功能(管理员) 20 min 2. 前端去浏览浏览接口、查看接口文档、申请签名(注册) 20 min 3. 在线调试(用户) 20 min 4. 统计用户调用接口的次数 20 min (下次一定) 5. 优化系统 - API 网关 20 min (下次一定) ## 四、开发接口发布和下线功能(时间点 09:07-37:54) ps.前端登录页面的手机号登录,其他按钮的删除,交给大家实现。 看看我们之前的接口管理页面(不急着启动,看看下图所示就好)。ZQrfE/sCxk5l2HPzrk0ji4jR2Ph837ekIgMbdJHkyJI=MHQxthdjzFhIWjS4Nn+sBZ3SJfic4DfvRwDIcKLFTjo= ![[l1xzCVtM6qXyBr29-94f23973.webp]] 这个页面之前是给管理员使用的,现在我们要开发一个用于发布和下线接口的功能。 本质上来说,就是改变每条接口数据的状态。在设计接口信息表时,之前已经预留了一个状态字段`status`。j3nSzs+NO5SqQKOeNTdarf4VPxB+8MbZdVOU86YsPf4=ZQrfE/sCxk5l2HPzrk0ji4jR2Ph837ekIgMbdJHkyJI= ![[pyAcgAJ5IZqTyFmI-8a07f391.webp]] 其中,关闭和开启分别对应接口的下线和上线。只有状态为 1 的接口才可以被用户调用,否则将无法调用。 现在我们先开发后台,开发完后台之后,开发前端的时间会更灵活一些,建议大家优先把核心功能做出来,不要去纠结那些界面。sZkzRi3MTD+un8ei2gjakRryTSmKof47t4VavacNfrE=ZQrfE/sCxk5l2HPzrk0ji4jR2Ph837ekIgMbdJHkyJI= ### 1. 功能设计(时间点 10:34-12:50) #### 1.1 功能设计讲解 现在让我们思考一下需要开发哪些后台接口。那就是发布接口和下线接口。大致规划一下思路:nxygd+ImutUjNSlTu1uMISbMTMWPTt4PJRfqBmRyZn8=j3nSzs+NO5SqQKOeNTdarf4VPxB+8MbZdVOU86YsPf4= 发布接口:这个接口需要执行哪些任务呢?首先需要验证接口是否存在,然后判断接口是否可调用,否则访问接口都是 404,影响用户体验。接着,如果接口可以调用,我们需要修改数据库中该接口的状态为 1,表示接口已经被发布,状态默认为 0(关闭)。 下线接口:你可以为其新增一个状态字段。例如,使用 1 表示开启,使用 2 表示下线。通过这个新字段,可以清晰地区分接口状态。当状态为 0 时,表示该接口还没有进行任何处理,看大家自己的考虑。我们这里就直接使用 0 和 1 来表示状态,不再添加额外的状态字段,大家可以按照自己的需求进行设计。对于下线接口,校验接口是否存在也是和发布接口类似的,但是下线接口无需判断接口是否可调用。 另外,还需注意的一点是仅管理员可操作这两个接口,这点需要特别注意,防止用户越权操作。以上就是这两个接口的基本设计。y0KKJH83GHzBF93m63zbbBynhjavwWswKIbe+8lmuns=y0KKJH83GHzBF93m63zbbBynhjavwWswKIbe+8lmuns= 接下来, 我们将根据这个设计开始编写代码。整体来说,比较简单。对于业务逻辑等方面,大家可以自行思考。因为所有的业务和系统都是灵活的。我们开发的这个 API 接口开放平台只是一个简单的标准而已。但是具体的业务细节还是需要根据实际情况来进行调整。 #### 1.2 功能设计总结 **后台接口:**MHQxthdjzFhIWjS4Nn+sBZ3SJfic4DfvRwDIcKLFTjo=j3nSzs+NO5SqQKOeNTdarf4VPxB+8MbZdVOU86YsPf4= **发布接口(仅管理员可操作)** 1. 校验该接口是否存在 2. 判断该接口是否可以调用 3. 修改接口数据库中的状态字段为 1 **下线接口(仅管理员可操作)**MHQxthdjzFhIWjS4Nn+sBZ3SJfic4DfvRwDIcKLFTjo=y0KKJH83GHzBF93m63zbbBynhjavwWswKIbe+8lmuns= 1. 校验该接口是否存在 2. 修改接口数据库中的状态字段为 0 ### 2. 后端项目开发(时间点 12:50-29:22) #### 2.1 创建发布和下线接口(时间点 12:50-16:47) 把 yuapi-client-sdk 项目和 yuapi-interface 项目,放到 yuapi-backend 项目中,方便操作。 ![[RMo2mMn3fTlMlAqI-4483de45.png]] **或者**直接拉取🐟的 [项目](http://gitlab.code-nav.cn/root/yuapi-backend/-/tree/2a9a6868360b63400b0dc80fb81ffaed089a2954)。QFU7mhNWUEojH1P/mbZkTNJwHPK/zMECeOY+OMwV/3U=dlIMZh5jzCLjaFePUcXqKAODVjw+KvlmqOtoVYi2eV8=I/mv7JqPu0KdBgBsR4rkFWPOT/qJ5Yd7ZLlmeKodCQ4= ![[kVbsYFSROMfJDVLe-e50c1afe.webp]] 找到 InterfaceInfoController.java,顺便修改注释为`接口管理`。 ![[53pTRJwg4c4cpddW-23006463.webp]] 往下滑,复制更新接口。 ![[kY30FUdFocAR4hws-d8cb4262.webp]] 粘贴两个在此处。sZkzRi3MTD+un8ei2gjakRryTSmKof47t4VavacNfrE=MHQxthdjzFhIWjS4Nn+sBZ3SJfic4DfvRwDIcKLFTjo= ![[VxUQroWfJla8b3uO-041dacc4.webp]] 改成发布和下线。 ![[YNln1vh5vMRIii05-c7edd5f9.webp]] 然后**这两个接口接收什么参数呢?**其实只需接收接口的 id 即可。比如,我想要发布 id 唯一的接口,只需传递这个 id 作为参数即可,这样就可以清楚地表示要对哪个接口进行发布操作。 这里我们新建一个通用的 request: 复制 common 目录下的 DeleteRequest.java,粘贴到 common 目录下,并重命名为`IdRequest`。dlIMZh5jzCLjaFePUcXqKAODVjw+KvlmqOtoVYi2eV8=sZkzRi3MTD+un8ei2gjakRryTSmKof47t4VavacNfrE= ![[Bn8pcz7KGH26OKQJ-19484b16.webp]] `IdRequest`就是封装了 id 这个参数,没有别的意思,它将一个基本类型封装成一个对象,这样便于我们进行 json 参数传递。 有同学提议:不封装,直接加参数也可以的,但是得去掉 @RequestBody 注解。j3nSzs+NO5SqQKOeNTdarf4VPxB+8MbZdVOU86YsPf4=MHQxthdjzFhIWjS4Nn+sBZ3SJfic4DfvRwDIcKLFTjo= 🐟建议:还是封装一下比较好,因为这样的话可以使所有的 post 接口保持统一。 回到`InterfaceInfoController.java`,把这两个接口的接收参数都改一下。 ![[uSRhQlZMXIiv82e8-a6600abd.webp]] 这两个接口只能管理员能调用,我们要给接口打上注解。 ![[EXPCJ2Saz2rgOnVz-a5b43a14.webp]] 这个注解是怎么实现的呢?这是🐟自己写的权限校验切面注解,它对应的实现方法在这里。8t5+VJlIX2Lzofl38AYV5bnrc6jCeMh9kFLnSW4W2fY=dlIMZh5jzCLjaFePUcXqKAODVjw+KvlmqOtoVYi2eV8= ![[7fnIQLB55ylzEuZe-63fc2dd5.webp]] 这个功能的原理非常简单: 首先获取当前登录用户的信息,然后判断用户是否具有管理员权限。如果没有权限,就会直接抛出权限异常;如果有权限,就会继续执行后续操作。这种方式被称为 AOP 切面的基本应用之一。对于学习 Spring 的同学来说,这个功能一定要掌握好,它能够帮助你更好地理解和应用 AOP 的概念。nxygd+ImutUjNSlTu1uMISbMTMWPTt4PJRfqBmRyZn8=ZQrfE/sCxk5l2HPzrk0ji4jR2Ph837ekIgMbdJHkyJI= #### 2.2 发布接口业务逻辑(时间点 16:47-28:55) 我们开始编写发布接口的业务逻辑,根据功能设计进行编写代码。 ![[05VudQRopM8wst2D-466440a2.png]] 先校验该接口是否存在。 ```java /** * 发布 * * @param idRequest * @param request * @return */ // 声明该方法为一个HTTP POST请求处理方法,处理路径是"/online",用于发布接口 @PostMapping("/online") // 该接口仅管理员可用 @AuthCheck(mustRole = "admin") public BaseResponse onlineInterfaceInfo(@RequestBody IdRequest idRequest, HttpServletRequest request) { // 如果id为null或者id小于等于0 if (idRequest == null || idRequest.getId() <= 0) { // 抛出业务异常,表示请求参数错误 throw new BusinessException(ErrorCode.PARAMS_ERROR); } // 1.校验该接口是否存在 // 获取idRequest对象的id属性值 long id = idRequest.getId(); // 根据id查询接口信息数据 InterfaceInfo oldInterfaceInfo = interfaceInfoService.getById(id); // 如果查询结果为空 if (oldInterfaceInfo == null) { // 抛出业务异常,表示未找到数据 throw new BusinessException(ErrorCode.NOT_FOUND_ERROR); } // 2.判断该接口是否可以调用 } ``` 接着判断该接口是否可以调用,怎么判断呢?上次我们开发了一个客户端的 SDK,直接使用它就可以了;j3nSzs+NO5SqQKOeNTdarf4VPxB+8MbZdVOU86YsPf4=nxygd+ImutUjNSlTu1uMISbMTMWPTt4PJRfqBmRyZn8= 复制在 yuapi-interface 项目中的依赖。 ![[AHXAgExyU4agkNBx-c62c541a.webp]] 粘贴到 yuapi-backend 项目中。j3nSzs+NO5SqQKOeNTdarf4VPxB+8MbZdVOU86YsPf4=y0KKJH83GHzBF93m63zbbBynhjavwWswKIbe+8lmuns= ![[bcupyJbUhNXuKcmm-43a148a5.webp]] 去 application.yml 编写客户端的配置。 ![[2iCmMxkF9YD5n1f5-04ff6df6.webp]] 然后在 InterfaceInfoController.java 引入客户端的实例。 ![[lUPnhTezhcnr1xow-578e927b.webp]] 继续编写发布接口的判断该接口是否可以调用。8t5+VJlIX2Lzofl38AYV5bnrc6jCeMh9kFLnSW4W2fY=ZQrfE/sCxk5l2HPzrk0ji4jR2Ph837ekIgMbdJHkyJI= ```java /** * 发布 * * @param idRequest * @param request * @return */ @PostMapping("/online") @AuthCheck(mustRole = "admin") public BaseResponse onlineInterfaceInfo(@RequestBody IdRequest idRequest, HttpServletRequest request) { if (idRequest == null || idRequest.getId() <= 0) { throw new BusinessException(ErrorCode.PARAMS_ERROR); } // 1.校验该接口是否存在 long id = idRequest.getId(); InterfaceInfo oldInterfaceInfo = interfaceInfoService.getById(id); if (oldInterfaceInfo == null) { throw new BusinessException(ErrorCode.NOT_FOUND_ERROR); } // 2.判断该接口是否可以调用 // 创建一个User对象(这里先模拟一下,搞个假数据) com.yupi.yuapiclientsdk.model.User user = new com.yupi.yuapiclientsdk.model.User(); // 设置user对象的username属性为"test" user.setUsername("test"); // 通过yuApiClient的getUsernameByPost方法传入user对象,并将返回的username赋值给username变量 String username = yuApiClient.getUsernameByPost(user); // 如果username为空或空白字符串 if (StringUtils.isBlank(username)) { // 抛出系统错误的业务异常,表示系统内部异常,并附带错误信息"接口验证失败" throw new BusinessException(ErrorCode.SYSTEM_ERROR, "接口验证失败"); } // 创建一个InterfaceInfo对象 InterfaceInfo interfaceInfo = new InterfaceInfo(); // 设置interfaceInfo的id属性为id interfaceInfo.setId(id); // 3.修改接口数据库中的状态字段为 1 interfaceInfo.setStatus(); } ``` --- 还有一个小问题需要**注意**,我们的接口名称都是固定的,而不是通过地址来映射到相应的接口,所以只能通过方法名调用接口。先记录下这个问题,后续开发过程中解决它,本期先走通整个流程(下次一定)。ZQrfE/sCxk5l2HPzrk0ji4jR2Ph837ekIgMbdJHkyJI=nxygd+ImutUjNSlTu1uMISbMTMWPTt4PJRfqBmRyZn8= ![[lZxuEkhQVBCILxLs-b5795e17.webp]] --- 这里我们的状态应该是 1,建议大家可以用枚举值来表示。sZkzRi3MTD+un8ei2gjakRryTSmKof47t4VavacNfrE=dlIMZh5jzCLjaFePUcXqKAODVjw+KvlmqOtoVYi2eV8= ![[GM6TbnZHmgaW7VwT-341b0c52.png]] 复制 PostGenderEnum.java(帖子性别枚举),粘贴到 enums 包下,并重命名为`InterfaceInfoStatusEnum`(接口信息状态枚举)。 ![[EQLOktnOJ2UMhCgM-ff4fe4e1.webp]] 修改一下。 ![[JnuyWalanHNv8HJf-fcff56ab.webp]] 回到 InterfaceInfoController.java 继续编写代码。I/mv7JqPu0KdBgBsR4rkFWPOT/qJ5Yd7ZLlmeKodCQ4=8t5+VJlIX2Lzofl38AYV5bnrc6jCeMh9kFLnSW4W2fY= ```java /** * 发布 * * @param idRequest * @param request * @return */ @PostMapping("/online") @AuthCheck(mustRole = "admin") public BaseResponse onlineInterfaceInfo(@RequestBody IdRequest idRequest, HttpServletRequest request) { if (idRequest == null || idRequest.getId() <= 0) { throw new BusinessException(ErrorCode.PARAMS_ERROR); } // 1.校验该接口是否存在 long id = idRequest.getId(); InterfaceInfo oldInterfaceInfo = interfaceInfoService.getById(id); if (oldInterfaceInfo == null) { throw new BusinessException(ErrorCode.NOT_FOUND_ERROR); } // 2.判断该接口是否可以调用 com.yupi.yuapiclientsdk.model.User user = new com.yupi.yuapiclientsdk.model.User(); user.setUsername("test"); String username = yuApiClient.getUserNameByPost(user); if (StringUtils.isBlank(username)) { throw new BusinessException(ErrorCode.SYSTEM_ERROR, "接口验证失败"); } InterfaceInfo interfaceInfo = new InterfaceInfo(); interfaceInfo.setId(id); // 3.修改接口数据库中的状态字段为上线 interfaceInfo.setStatus(InterfaceInfoStatusEnum.ONLINE.getValue()); // 调用interfaceInfoService的updateById方法,传入interfaceInfo对象,并将返回的结果赋值给result变量 boolean result = interfaceInfoService.updateById(interfaceInfo); // 返回一个成功的响应,响应体中携带result值 return ResultUtils.success(result); } ``` 这里的 request 对象没有用到,先留着,说不定后面会用到。 ![[yGGUBbsvDIzHE0yW-6c209a69.webp]] #### 2.3 下线接口业务逻辑(时间点 28:55-29:22) 接下来我们写一下下线接口的业务逻辑,直接把发布接口的业务逻辑复制粘贴到下线接口中。 ![[GJjMjAfJ91Yht0mm-6885ad51.webp]] 然后同样的逻辑判断 id 是否存在、判断这个接口是否存在,判断接口是否可以调用就不需要了,把上线的状态改为下线的状态。 ![[8zLQP2Vn3FiJ3EFW-bc2c2cf8.webp]] 接口已经完成且没有问题,接下来,启动后端项目(记得先启动 redis),无需进行测试,因为这是一个相对简单的功能。j3nSzs+NO5SqQKOeNTdarf4VPxB+8MbZdVOU86YsPf4=dlIMZh5jzCLjaFePUcXqKAODVjw+KvlmqOtoVYi2eV8= ![[zZ8W5D5nKbNh95Ad-551cd2a2.png]] ### 3. 前端项目开发(时间点 29:22-37:54) #### 3.1 增加发布和下线按钮(时间点 29:22-37:54) 接下来写一下前端,先把所有管理员操作的页面封装到一起; 在 page 目录下新建 Admin 目录,把 InterfaceInfo 目录放进去。 ![[MGUKQvc1Q8WkdXo1-3a400655.webp]] 然后去 routes.ts 修改组件路径(这样路由和对应的组件路径一样,会规范一点)。 ![[oiskDMWPFIp8A3Wb-acb1ed8c.webp]] 开始在前端页面的操作区增加发布和下线的按钮。I/mv7JqPu0KdBgBsR4rkFWPOT/qJ5Yd7ZLlmeKodCQ4=dlIMZh5jzCLjaFePUcXqKAODVjw+KvlmqOtoVYi2eV8= ![[w0y7XELmQQbDt4gp-f13a95b6.webp]] 找到对应的页面 InterfaceInfo 目录下的 index.tsx,复制删除按钮。 ![[Z9TLrEWBc0D2BMcO-71756f51.webp]] 粘贴到此处进行修改。 ![[g99vgYcqfIliDJno-26d94939.webp]] 在终端输入`yarn run dev`启动前端,访问 [http://localhost:8000。](https://www.codefather.cn/course/1790979723916521474/section/1790982983314354177?type=#heading-0http://localhost:8000%E3%80%82)I/mv7JqPu0KdBgBsR4rkFWPOT/qJ5Yd7ZLlmeKodCQ4=QFU7mhNWUEojH1P/mbZkTNJwHPK/zMECeOY+OMwV/3U=I/mv7JqPu0KdBgBsR4rkFWPOT/qJ5Yd7ZLlmeKodCQ4= ![[ZujVostOkbHR9yvd-551f1fb3.webp]] 登录后,查看新增的按钮。 ![[yCDhk5VUzje5NDtV-79b4eeb0.webp]] **💡** **是否可以同时展示发布和下线这两个按钮呢?** 最好不要同时展示,如果一个接口已经发布了,我们只展示下线按钮会更好一些。 所以这里要加一个判断,在这个渲染函数中,能拿到当前这条记录的对象,它是一个 record,类型就是我们的接口信息,所以我们可以直接根据 record 的 status 来判断。QFU7mhNWUEojH1P/mbZkTNJwHPK/zMECeOY+OMwV/3U=MHQxthdjzFhIWjS4Nn+sBZ3SJfic4DfvRwDIcKLFTjo= ![[hrdFoNWZuXuWB9V3-20fce6f4.webp]] 回到前端页面,刷新一下,因为我们所有的接口都是未发布,所以现在能看见发布按钮。 ![[NDOhTHYv1zmHRMg3-41fad644.webp]] 修改下线按钮和删除按钮的颜色,使用 Ant Design Pro 给我们提供的一个文字按钮组件。 ![[3L33PsHzwb9C8WQ8-e57ea77b.webp]] 回到前端页面,鼠标放到删除按钮,就会变色。dlIMZh5jzCLjaFePUcXqKAODVjw+KvlmqOtoVYi2eV8=8t5+VJlIX2Lzofl38AYV5bnrc6jCeMh9kFLnSW4W2fY= ![[qfZLzEOHg9bSd7pr-ef73b1e5.webp]] 来实现发布和下线按钮的功能。大家还记得**怎么调用后端接口的函数?** 现在新加了两个接口,前端应该怎么办?ZQrfE/sCxk5l2HPzrk0ji4jR2Ph837ekIgMbdJHkyJI=I/mv7JqPu0KdBgBsR4rkFWPOT/qJ5Yd7ZLlmeKodCQ4= 利用 openapi 生成即可,前端不需要写任何接口调用的方法,在终端执行`yarn run openapi`。 ![[ltzsBgnoZy0MeJPB-5a274e94.webp]] 然后直接复制删除节点的方法,粘贴到删除节点上面。ZQrfE/sCxk5l2HPzrk0ji4jR2Ph837ekIgMbdJHkyJI=nxygd+ImutUjNSlTu1uMISbMTMWPTt4PJRfqBmRyZn8= ![[yH7mRhYUeeZSVsC7-5adb5a60.webp]] 修改成发布节点。 ```java /** * 发布接口 * * @param record */ const handleOnline = async (record: API.IdRequest) => { // 显示正在发布的加载提示 const hide = message.loading('发布中'); // 如果接口数据为空,直接返回true if (!record) return true; try { // 调用发布接口的POST请求方法 await onlineInterfaceInfoUsingPOST({ // 传递接口的id参数 id: record.id }); hide(); // 显示操作成功的提示信息 message.success('操作成功'); // 重新加载数据 actionRef.current?.reload(); // 返回true表示发布成功 return true; } catch (error: any) { hide(); // 显示操作失败的错误提示信息 message.error('操作失败,' + error.message); // 返回false表示发布失败 return false; } }; ``` ![[KRsPUzmw6StuBAAu-e1e6b03e.webp]] 复制发布节点的方法,粘贴到发布节点下面,然后修改成下线节点。 ![[p6OfiVzmG5oaV5VJ-6a1b3f3a.webp]] ```java /** * 下线接口 * * @param record */ const handleOffline = async (record: API.IdRequest) => { // 显示正在下线的加载提示 const hide = message.loading('发布中'); // 如果接口数据为空,直接返回true if (!record) return true; try { // 调用下线接口的POST请求方法 await offlineInterfaceInfoUsingPOST({ // 传递接口的id参数 id: record.id }); hide(); // 显示操作成功的提示信息 message.success('操作成功'); // 重新加载数据 actionRef.current?.reload(); // 返回true表示下线成功 return true; } catch (error: any) { hide(); // 显示操作失败的错误提示信息 message.error('操作失败,' + error.message); // 返回false表示下线失败 return false; } }; ``` 把发布和下线按钮改成调用对应的函数。 ![[Fc91E3yF7Ivb3sS8-5adc983f.webp]] idea 另开一个窗口,打开 yuapi-interface 项目运行。 ![[rbSVU2nMcPBCVAAW-93c36216.webp]] yuapi-interface 项目如果没有运行,点击发布按钮会弹出未连接。sZkzRi3MTD+un8ei2gjakRryTSmKof47t4VavacNfrE=dlIMZh5jzCLjaFePUcXqKAODVjw+KvlmqOtoVYi2eV8= ![[INyNYxrvagOedohT-79b00574.webp]] --- yuapi-interface 项目是模拟接口项目,我们开发的主要是后台管理系统和用户前台,但模拟接口也要启动,可见之前设计的流程图。MHQxthdjzFhIWjS4Nn+sBZ3SJfic4DfvRwDIcKLFTjo=dlIMZh5jzCLjaFePUcXqKAODVjw+KvlmqOtoVYi2eV8= ![[PHs0DY7gmvlr1qn0-e7ffc99c.webp]] --- 回到前端页面试一下,点击发布按钮,提示`操作成功`,状态变为开启,并显示了下线按钮。MHQxthdjzFhIWjS4Nn+sBZ3SJfic4DfvRwDIcKLFTjo=ZQrfE/sCxk5l2HPzrk0ji4jR2Ph837ekIgMbdJHkyJI= ![[mRaSDN7kMUbHWwLT-6f41aeb3.webp]] 点击下线按钮,提示`操作成功`,状态变为关闭,并显示了发布按钮。 ![[AZSDNN9xfR1Eon2E-93a86b03.webp]] #### 3.2 解决按钮同时显示的问题 这时你发现,诶,我们之前不是写了不能同时显示发布和下线按钮吗? 虽然说刷新之后显示正常,但这还是太怪了,有两种方法:MHQxthdjzFhIWjS4Nn+sBZ3SJfic4DfvRwDIcKLFTjo=8t5+VJlIX2Lzofl38AYV5bnrc6jCeMh9kFLnSW4W2fY= 1. **设置不同的 key** - 发布和下线的按钮都有 key 属性,默认是 config,设置成不一样的即可,原因是,在 React 中,如果列表中的元素需要进行增删操作,需要为每个元素指定唯一的 key。这是因为 React 使用 key 来识别列表中的每个元素,以便在更新列表时能够准确地判断新旧元素的对应关系,从而提高渲染性能。 - 给发布按钮和下线按钮设置不同的 key 是解决重复渲染问题的一种方法。通过设置不同的 key 值,React 在进行新旧对比时会识别出这是两个不同的元素,从而确保它们能够正确地渲染和更新。 所以,可以通过给发布按钮和下线按钮设置不同的 key 值来解决按钮重复渲染的问题,确保 React 能够正确地进行虚拟 DOM 的对比和更新。QFU7mhNWUEojH1P/mbZkTNJwHPK/zMECeOY+OMwV/3U=nxygd+ImutUjNSlTu1uMISbMTMWPTt4PJRfqBmRyZn8= ![[Ptpm1QECZWLm3eTl-68b96134.webp]] 1. **修改条件渲染逻辑** 代码中是通过条件来渲染按钮,重新修正代码:sZkzRi3MTD+un8ei2gjakRryTSmKof47t4VavacNfrE=y0KKJH83GHzBF93m63zbbBynhjavwWswKIbe+8lmuns= - 首先判断状态是否为 0,如果是 0,则渲染发布按钮; - 如果状态为 1,则渲染下线按钮;其他状态的情况下,不进行任何渲染。 这样可以根据状态变化正确渲染对应的按钮,并且不会出现重复按钮的问题。 ![[Xq1tkKxBzSdAGOTN-a4f7810b.webp]] 使用以上两种方法都能解决渲染问题。 然后我们点击发布按钮,页面显示下线按钮,发布按钮也被隐藏。 ![[PX7NVzkJHnLOKcdY-5f3fd0d9.webp]] 点击下线按钮,显示发布按钮,不会显示多个下线按钮,下线按钮被隐藏了。 ![[eXo9KHp3XubV7iza-769b1d82.webp]] 搞定咯(~ ̄▽ ̄)~QFU7mhNWUEojH1P/mbZkTNJwHPK/zMECeOY+OMwV/3U=QFU7mhNWUEojH1P/mbZkTNJwHPK/zMECeOY+OMwV/3U= ## 五、前端开发浏览接口页(时间点 37:54-54:51) ### 1. 创建浏览接口页(时间点 37:54-40:26) 现在要新建一个给用户看的页面,之前我们是没有主页的。sZkzRi3MTD+un8ei2gjakRryTSmKof47t4VavacNfrE=y0KKJH83GHzBF93m63zbbBynhjavwWswKIbe+8lmuns= 删掉 Admin.tsx,在 pages 目录下新建`Index`目录。 ![[3YifLVIV3YrlLyWs-288b295d.webp]] 把 Welcome.tsx 拖到 Index 目录下,并修改名为`index.tsx`。sZkzRi3MTD+un8ei2gjakRryTSmKof47t4VavacNfrE=dlIMZh5jzCLjaFePUcXqKAODVjw+KvlmqOtoVYi2eV8= ![[PJlWgxCR8SXlYNcp-96ad321b.webp]] 去 route.ts 修改路由,取消掉之前欢迎页的注释,然后把它放到最上面。 ![[9Qv9e5yViiSVuwrk-4320d3ba.webp]] 修改路径、名称、组件路径。 ![[Ee8xEvOY6sbuGl9b-7351906c.png]] 访问 [http://localhost:8000,主页就展示出来,它默认提供了这些东西:了解](https://www.codefather.cn/course/1790979723916521474/section/1790982983314354177?type=#heading-0http://localhost:8000%EF%BC%8C%E4%B8%BB%E9%A1%B5%E5%B0%B1%E5%B1%95%E7%A4%BA%E5%87%BA%E6%9D%A5%EF%BC%8C%E5%AE%83%E9%BB%98%E8%AE%A4%E6%8F%90%E4%BE%9B%E4%BA%86%E8%BF%99%E4%BA%9B%E4%B8%9C%E8%A5%BF%EF%BC%9A%E4%BA%86%E8%A7%A3) umi、了解 ant design...8t5+VJlIX2Lzofl38AYV5bnrc6jCeMh9kFLnSW4W2fY=QFU7mhNWUEojH1P/mbZkTNJwHPK/zMECeOY+OMwV/3U= ![[DS12VAdRyRqCOSva-536c2cdd.webp]] 我们不需要这些,改成我们自己的浏览接口页。 ### 2. 修改浏览接口页(时间点 40:26-54:51) 回到主页,删除多余的内容,留下 PageContainer。 - PageContainer:是 Ant Design Pro 中提供的一个组件,用于快速构建页面的容器。它提供了一些常用的布局和功能,例如面包屑导航、页面标题、操作区域等,可以帮助我们快速搭建页面的基本结构。 ![[YshpgO0X3gl8I1GB-6b878cbf.webp]] ```javascript import { PageContainer } from '@ant-design/pro-components'; import React from 'react'; const Index: React.FC = () => { return ( ); }; export default Index; ``` 通过 title 设置页面标题,在 PageContainer 随便写一些内容。 ```javascript import { PageContainer } from '@ant-design/pro-components'; import React from 'react'; const Index: React.FC = () => { return ( 阿巴巴 ); }; export default Index; ``` 回到前端页面查看修改情况。 ![[sykaKQ8qigvvg23V-8b6de20e.webp]] 直接让浏览接口页像管理页一样,将所有的接口以列表形式展示给用户;j3nSzs+NO5SqQKOeNTdarf4VPxB+8MbZdVOU86YsPf4=j3nSzs+NO5SqQKOeNTdarf4VPxB+8MbZdVOU86YsPf4= 去 `ant design 组件库`找一下列表(list)组件。 ![[S09YkfyJFnP8BIKK-b68a027f.webp]] 选一个精简一点的,然后点击显示代码按钮。nxygd+ImutUjNSlTu1uMISbMTMWPTt4PJRfqBmRyZn8=j3nSzs+NO5SqQKOeNTdarf4VPxB+8MbZdVOU86YsPf4= ![[VxVxbPgF47KoCigc-f8072dd0.webp]] 找到示例代码。 ![[2Pglb8rvMFpPDxwY-24edf2eb.webp]] ```html ( edit, more]} > } title={{item.name?.last}} description="Ant Design, a design language for background applications, is refined by Ant UED Team" />
content
)} /> ``` 放到这里。 ![[NuFi9UXec76SYPWI-15962282.webp]] 这里还有什么页面的加载状态(loading),去`ant design 组件库`复制。 ![[hk3Rv8EIvUsb2pvC-b8673628.webp]] ```java const [initLoading, setInitLoading] = useState(true); const [loading, setLoading] = useState(false); const [data, setData] = useState([]); const [list, setList] = useState([]); ``` 粘贴到主页中。 ![[ajq9395bDUTJiHG6-282398ec.webp]] 然后进行修改。MHQxthdjzFhIWjS4Nn+sBZ3SJfic4DfvRwDIcKLFTjo=I/mv7JqPu0KdBgBsR4rkFWPOT/qJ5Yd7ZLlmeKodCQ4= ```typescript import { PageContainer } from '@ant-design/pro-components'; import React, { useEffect, useState } from 'react'; import { List, message } from 'antd'; import { listInterfaceInfoByPageUsingGET } from '@/services/yuapi-backend/interfaceInfoController'; /** * 主页 * @constructor */ const Index: React.FC = () => { // 使用 useState 和泛型来定义组件内的状态 // 加载状态 const [loading, setLoading] = useState(false); // 列表数据 const [list, setList] = useState([]); // 总数 const [total, setTotal] = useState(0); // 定义异步加载数据的函数 const loadData = async (current = 1, pageSize = 5) => { // 开始加载数据,设置 loading 状态为 true setLoading(true); try { // 调用接口获取数据 const res = await listInterfaceInfoByPageUsingGET({ current, pageSize, }); // 将请求返回的数据设置到列表数据状态中 setList(res?.data?.records ?? []); // 将请求返回的总数设置到总数状态中 setTotal(res?.data?.total ?? 0); // 捕获请求失败的错误信息 } catch (error: any) { // 请求失败时提示错误信息 message.error('请求失败,' + error.message); } // 数据加载成功或失败后,设置 loading 状态为 false setLoading(false); }; useEffect(() => { // 页面加载完成后调用加载数据的函数 loadData(); }, []); return ( // 使用 antd 的 PageContainer 组件作为页面容器 ( 查看]}> {item.name}} description={item.description} /> ); }} // 分页配置 pagination={{ // 自定义显示总数 // eslint-disable-next-line @typescript-eslint/no-shadow showTotal(total: number) { return '总数:' + total; }, // 每页显示条数 pageSize: 5, // 总数,从状态中获取 total, // 切换页面触发的回调函数 onChange(page, pageSize) { // 加载对应页面的数据 loadData(page, pageSize); }, }} /> ); }; export default Index; ``` 请注意,修改后的`index.tsx`中有两个将页数设置为数字 5 的设置,也就是多次使用重复的数字。这里需要记住的是,当我们在代码中使用魔法值(即凭空出现的数值)时,如果我们发现需要在多个地方修改这个数值,建议将其提取为一个常量。 **为什么要提取为常量呢?**因为使用常量可以提高代码的可维护性和可读性。通过将魔法值提取为常量,然后就可以修改常量的值来一次性修改多处使用该值的地方,而不必逐个搜索和修改每个出现的地方。nxygd+ImutUjNSlTu1uMISbMTMWPTt4PJRfqBmRyZn8=j3nSzs+NO5SqQKOeNTdarf4VPxB+8MbZdVOU86YsPf4= ![[HBtgvmizf5VLs5Ko-bc0ffbbf.webp]] 修改好后,回到前端页面,查看浏览接口页面的效果展示。 ![[h7DS6lep7WzR4aDj-0339d9fd.webp]] 点击页数,查看数据是否正常显示。 ![[0Zy8P9MTLIyluB5u-3b1aa04a.webp]] ## 六、前端开发接口文档页(时间点 54:51-01:10:30) ### 1. 创建浏览接口文档页(时间点 54:51-01:00:29) 复制 Index 目录,粘贴到 page 目录下,并重命名为`InterfaceInfo`。 ![[jnl2FINCi8g8mGKj-1c67e606.webp]] 去 route.ts 添加路由,动态路由可以查看官网 —— [UmiJS](https://umijs.org/docs/guides/routes#%E5%8A%A8%E6%80%81%E8%B7%AF%E7%94%B1),给这个路由新增两个参数: - 让这个路由可以接收动态参数,在点击`查看`之后可以跳到对应的接口页面,通过 id 来区分不同的接口。 - - path: '/interface\_info/:id':定义了路由的路径,其中 :id 是一个参数占位符,表示在实际路径中可以传递一个具体的ID值作为参数。 - 让这个页面在菜单栏中隐藏,查看接口不需要放在菜单栏上。 - - hideInMenu: true:表示该路由在菜单中是否隐藏,如果设置为 true,则该路由不会在菜单中显示。 ![[Gi2QRySE53069ni9-ce4a472c.webp]] 现在我们要做的事情是点击`查看`之后,读取到这条数据的 id,然后跳到对应的页面。 先去修改超链接。 ![[KuLRHsjLg7aOMoQv-fc7cc1e1.webp]] 回到前端页面,点击`查看`,可以看见地址栏上的 url 变了,它已经跳转到查看接口文档页面。 ![[9MbMItTiRcezvPUS-e7e3be9c.webp]]访问 [http://localhost:8000,点击列表项,也能正常跳转。](https://www.codefather.cn/course/1790979723916521474/section/1790982983314354177?type=#heading-0http://localhost:8000%EF%BC%8C%E7%82%B9%E5%87%BB%E5%88%97%E8%A1%A8%E9%A1%B9%EF%BC%8C%E4%B9%9F%E8%83%BD%E6%AD%A3%E5%B8%B8%E8%B7%B3%E8%BD%AC%E3%80%82) ![[pLW68E6OWjigwRtt-a67f4824.webp]] ### 2. 修改浏览接口文档页(时间点 01:00:29-01:04:51) 现在来改一下查看单个接口文档的页面,需要从 url 参数中拿到这个 id,然后才知道要加载哪个接口信息。 **怎么拿到动态路由的参数呢?**查看官网 —— [UmiJS](https://umijs.org/docs/guides/routes#match-%E4%BF%A1%E6%81%AF)。8t5+VJlIX2Lzofl38AYV5bnrc6jCeMh9kFLnSW4W2fY=ZQrfE/sCxk5l2HPzrk0ji4jR2Ph837ekIgMbdJHkyJI= ![[OW3kpOxhktVyFQoZ-bea3d98d.webp]] 这里就使用 userMatch 来获取一下,进入查看接口文档页,进行修改。 ```java import { PageContainer } from '@ant-design/pro-components'; import React, { useEffect, useState } from 'react'; import { useMatch } from 'react-router'; /** * 主页 * @constructor */ const Index: React.FC = () => { const [loading, setLoading] = useState(false); const [data, setData] = useState(); // 使用useMatch钩子将当前URL与指定的路径模式/interface_info/:id进行匹配, // 并将匹配结果赋值给match变量 const match = useMatch('/interface_info/:id'); // 使用JSON.stringify将match变量的值转换为JSON字符串,并通过alert函数进行弹窗显示 alert(JSON.stringify(match)); const loadData = async (current = 1, pageSize = 5) => { // setLoading(true); // } // try { // const res = await getInterfaceInfoByIdUsingGET(id); // setData(res.data); // } catch (error: any) { // message.error('请求失败,' + error.message); // } // setLoading(false); }; useEffect(() => { loadData(); }, []); return ( ); }; export default Index; ``` 点击`查看`。 ![[UBnt8lgyQykJhmA0-309946b6.webp]] 弹出提示框,并打印出了 match 参数。dlIMZh5jzCLjaFePUcXqKAODVjw+KvlmqOtoVYi2eV8=ZQrfE/sCxk5l2HPzrk0ji4jR2Ph837ekIgMbdJHkyJI= ![[7dCCYUqQN0zfurpV-86841d6f.png]] 之前的 userMatch 可以获取整个页面路径的详细信息,但我们只需要拿动态路由的参数。 这里官网还提供了一个叫做 useParams 的钩子函数,通过使用 useParams,我们可以轻松地获取动态路由中的参数值,而无需关心整个页面路径的其他细节。sZkzRi3MTD+un8ei2gjakRryTSmKof47t4VavacNfrE=nxygd+ImutUjNSlTu1uMISbMTMWPTt4PJRfqBmRyZn8= ![[hrVW7eAtsAfTTFEC-bc52e162.webp]] 继续修改查看接口文档页面。 ```java import { getInterfaceInfoByIdUsingGET } from '@/services/yuapi-backend/interfaceInfoController'; import { PageContainer } from '@ant-design/pro-components'; import { message } from 'antd'; import React, { useEffect, useState } from 'react'; import { useParams } from 'react-router'; /** * 主页 * @constructor */ const Index: React.FC = () => { // 定义状态和钩子函数 const [loading, setLoading] = useState(false); const [data, setData] = useState(); // 使用 useParams 钩子函数获取动态路由参数 const params = useParams(); const loadData = async () => { // 检查动态路由参数是否存在 if (!params.id) { message.error('参数不存在'); return; } setLoading(true); try { // 发起请求获取接口信息,接受一个包含 id 参数的对象作为参数 const res = await getInterfaceInfoByIdUsingGET({ id: Number(params.id), }); // 将获取到的接口信息设置到 data 状态中 setData(res.data); } catch (error: any) { // 请求失败处理 message.error('请求失败,' + error.message); } // 请求完成,设置 loading 状态为 false,表示请求结束,可以停止加载状态的显示 setLoading(false); }; useEffect(() => { loadData(); }, []); return ( { // 将 data 对象转换为 JSON 字符串 JSON.stringify(data) } ); }; export default Index; ``` 修改后,回到前端页面,点击`查看`。 ![[KwJ47jsNj4r7SL4I-ba75cb91.webp]] 现在这个接口信息就获取到了。QFU7mhNWUEojH1P/mbZkTNJwHPK/zMECeOY+OMwV/3U=sZkzRi3MTD+un8ei2gjakRryTSmKof47t4VavacNfrE= ![[aBCdtSrjxtnjXlPp-ddbbad22.webp]] ### 3. 美化页面(时间点 01:04:51-01:10:30) 接下来对查看接口文档页面进行美化,用卡片进行包裹。ZQrfE/sCxk5l2HPzrk0ji4jR2Ph837ekIgMbdJHkyJI=y0KKJH83GHzBF93m63zbbBynhjavwWswKIbe+8lmuns= ![[xlJF6lDEfJ7Faqgy-45fdf10a.webp]] 回到前端页面查看,好看了一点🐶。 ![[vrbda9kaRbIzNc01-629e631b.webp]] 我们把里面的内容换成列表的形式,去 [官网](https://ant.design/components/descriptions-cn) 找一下描述列表组件; 这里就用最基本的展示,点击 ![[q2ONBMbYJnBWzgP0-975e3935.png]]显示代码按钮。 ![[vvg5rGgAe1ZIPKN5-5b4c862c.webp]] 复制示例代码。 ![[Ts1XQlLWUmpBWo6G-f78b3caa.webp]] ```xml Zhou Maomao 1810000000 Hangzhou, Zhejiang empty No. 18, Wantang Road, Xihu District, Hangzhou, Zhejiang, China ``` 粘贴到查看接口文档页面。 ![[enR2Sjc558wf87Vj-cf9e8bb2.webp]] 做一个判断,如果接口存在就展示列表,否则显示接口不存在;sZkzRi3MTD+un8ei2gjakRryTSmKof47t4VavacNfrE=QFU7mhNWUEojH1P/mbZkTNJwHPK/zMECeOY+OMwV/3U= `title={data.name}`将接口名称作为标题传递给 组件。 ![[FzNRbE9IJtIyLvNZ-d68b2ad5.webp]] 接下来填充列表内的数值,光标放在 InterfaceInfo 上,按[Ctrl+鼠标左键]。8t5+VJlIX2Lzofl38AYV5bnrc6jCeMh9kFLnSW4W2fY=QFU7mhNWUEojH1P/mbZkTNJwHPK/zMECeOY+OMwV/3U= ![[NKrKwGtkgFhs10oS-ff067ac7.webp]] 看一下接口信息的类型。 ![[ZfWQSjxTmKH7FdP3-08b2de2e.webp]] 根据接口信息,来修改列表,userId 就不需要了,用户不关心是谁发布的,因为我们这个不是共建平台。 ![[FRtbg54rjQV3fMU6-19e6b905.webp]] ```text {data.status} {data.description} {data.url} {data.method} {data.requestHeader} {data.responseHeader} {data.createTime} {data.updateTime} ``` 回到前端页面,点击`查看`。 ![[8HhK6B6wRAJKxzEi-18b9c7dc.webp]] 显示接口信息。y0KKJH83GHzBF93m63zbbBynhjavwWswKIbe+8lmuns=QFU7mhNWUEojH1P/mbZkTNJwHPK/zMECeOY+OMwV/3U= ![[JEQojhrx6TFNs482-f5996f20.webp]] 再换一个。 ![[NYYMNFTwPAV5xms1-515c56b0.webp]] 接口信息显示正常。 ![[LKMx9vKFFJgQoL6I-37aa6553.webp]] 不过这个列表展示还是有点太紧了,我们用参数来控制一下;QFU7mhNWUEojH1P/mbZkTNJwHPK/zMECeOY+OMwV/3U=nxygd+ImutUjNSlTu1uMISbMTMWPTt4PJRfqBmRyZn8= 使用 column,设置每行展示一条。 ![[a3eE3a9mrfbLyyHL-af6dbe43.webp]] 回到前端页面查看效果展示。nxygd+ImutUjNSlTu1uMISbMTMWPTt4PJRfqBmRyZn8=I/mv7JqPu0KdBgBsR4rkFWPOT/qJ5Yd7ZLlmeKodCQ4= ![[YLsguQABUKk8ebwg-6dc0ffa3.webp]] 我们这个状态还是显示数字,设置 1 显示 '开启',0 显示 '关闭'。 ![[0azhbfK1KQz0saxC-614adc73.webp]] 回到前端页面查看效果展示。 ![[JWt8XzrwvFFjRf2Z-48d7245f.webp]] ## 七、后端开发申请签名(时间点 01:10:30-01:28:38) ### 1. 开发申请签名(时间点 01:10:30-01:19:29) 现在用户已经能看到这个接口了,也能看到这个接口文档,接下来就要在线调用; 在线调用前必须要分配一个签名,上次我们自己造了一个签名和密钥,看一下数据库。QFU7mhNWUEojH1P/mbZkTNJwHPK/zMECeOY+OMwV/3U=ZQrfE/sCxk5l2HPzrk0ji4jR2Ph837ekIgMbdJHkyJI= ![[oPVJ99rR5LQaYlrK-c7d9fa8c.webp]] 现在我们可以给每个新注册的用户自动分配一个签名和密钥,去修改一下注册流程: 回到后端 yuapi-backend 项目,找到 UserServiceImpl.java 中的 userRegister。I/mv7JqPu0KdBgBsR4rkFWPOT/qJ5Yd7ZLlmeKodCQ4=8t5+VJlIX2Lzofl38AYV5bnrc6jCeMh9kFLnSW4W2fY= ![[Swf34zO4mD1vWif7-aa30c8b3.webp]] 在插入数据前增加分配 ak、sk,把插入数据变成第四步。 怎么给它随机分配?只要用加密算法生成即可。y0KKJH83GHzBF93m63zbbBynhjavwWswKIbe+8lmuns=ZQrfE/sCxk5l2HPzrk0ji4jR2Ph837ekIgMbdJHkyJI= ![[BE4whJ1ChyufbBae-4e2f8fea.webp]] 然后再把得到的值设置给用户,现在有个问题,我们之前的 user 没有这两个字段,ak、sk 是后来改表加进去的,所以要给 user 扩充点参数。 找到 User.java,补充 ak、sk。ZQrfE/sCxk5l2HPzrk0ji4jR2Ph837ekIgMbdJHkyJI=dlIMZh5jzCLjaFePUcXqKAODVjw+KvlmqOtoVYi2eV8= ![[k97jkxRJoT2qlPd0-35eb900e.webp]] 在 UserMapper.xml 也添加上 ak、sk。 ![[3Nz47DMg8QPR497H-48fbceaf.webp]] 现在就可以把得到的值设置给用户,回到 UserServiceImpl.java 中的 userRegister。 ![[XMlhnfiCvmoYVYnM-e4644db5.webp]] 重启后端项目。MHQxthdjzFhIWjS4Nn+sBZ3SJfic4DfvRwDIcKLFTjo=I/mv7JqPu0KdBgBsR4rkFWPOT/qJ5Yd7ZLlmeKodCQ4= ![[jgrYg2IWAsCFWMIf-c8c949a4.webp]] 访问 [http://localhost:7529/api/doc.html,然后注册一下。](http://localhost:7529/api/doc.html%EF%BC%8C%E7%84%B6%E5%90%8E%E6%B3%A8%E5%86%8C%E4%B8%80%E4%B8%8B%E3%80%82) ![[eCkUTqY7Wk5hhX5k-e455eb7f.webp]] 去数据库里看一下。 ![[gIya5ZWL8fCElJlR-37b80c84.webp]] 注册时自动分配 ak、sk 就搞定了。MHQxthdjzFhIWjS4Nn+sBZ3SJfic4DfvRwDIcKLFTjo=y0KKJH83GHzBF93m63zbbBynhjavwWswKIbe+8lmuns= ### 2. 创建真实数据(时间点 01:19:29-01:23:58) 现在用户也有签名了,就可以在线来调用这个接口,回到前端页面创建一个真实一点的数据,之前都是假数据。 ![[w7WDumFRUXnNMm8g-e4734b03.webp]] 鼠标右键选择`检查`→`网络`,等会看一下请求响应。 ![[SZcWDacZHh93ErL6-614760d4.webp]] 填写接口信息,这里的接口就用 yuapi-interface 项目中的 getUserNameByPost,填写完后点击`提交`。dlIMZh5jzCLjaFePUcXqKAODVjw+KvlmqOtoVYi2eV8=QFU7mhNWUEojH1P/mbZkTNJwHPK/zMECeOY+OMwV/3U= ps.这里的请求方法框可以做成下拉框。 ![[jv8B5H3XQnOoZX7u-a6466403.webp]] --- 上面的 url 可以去后端随机复制一个接口地址,点击 Endpoints 可以查看到接口地址; 选中接口,点击鼠标右键→Generate Request in HTTP Client。 ![[HDu1t7ofCRljIs2J-e81c230b.webp]] 生成客户端请求,然后复制粘贴到前端页面的 url 框中即可。 ![[CnQwuPHzYqJyFKya-6356f038.png]] --- 点击提交后,查看请求,它把双引号内文本进行了转译。 ![[C6DZKsX4SFqjyKEM-314d2b44.webp]] 去数据库查看数据,把这条数据放到最上面。QFU7mhNWUEojH1P/mbZkTNJwHPK/zMECeOY+OMwV/3U=QFU7mhNWUEojH1P/mbZkTNJwHPK/zMECeOY+OMwV/3U= ![[ol3rb3h1fo4ExsAh-8a4981c3.webp]] 先将第一条数据改为 23。 ![[Ru4a8wQSODTViAp7-024b26c1.webp]] 然后把这条数据 id 修改为 1。 ![[vJMiTVN6DKzXs0Cw-9ca47f6c.webp]] 回到前端页面,查看效果。y0KKJH83GHzBF93m63zbbBynhjavwWswKIbe+8lmuns=j3nSzs+NO5SqQKOeNTdarf4VPxB+8MbZdVOU86YsPf4= ![[kWMRW8D76gtyGc40-8021f95b.webp]] 查看接口文档展示效果,点击`查看`。 ![[Toi86BQOzcbl3d4Z-c7b01af3.webp]] ![[swC6PwncG8Rnn7RR-8817c45b.webp]] ps.创建时间、更新时间修改格式,交给大家实现,伙伴匹配系统已经带大家改过了。 ### 3. 补充参数(时间点 01:24:04-01:28:38) 这里发现接口信息里面没有请求参数,去后端补充一下。 ![[swC6PwncG8Rnn7RR-8817c45b.webp]] 更新下 db.sql 的字段。ZQrfE/sCxk5l2HPzrk0ji4jR2Ph837ekIgMbdJHkyJI=8t5+VJlIX2Lzofl38AYV5bnrc6jCeMh9kFLnSW4W2fY= ![[ZvscjwmvCLbZYAFq-ce31a728.webp]] 右键 interface\_info 表,选择 Modify Table。 ![[FjnqZ0dOwalj5We1-0a7f5b12.webp]] 添加请求参数字段。 ![[GbjRxtf5Q17QlhVh-eec06793.webp]] interface\_info 表就新增了请求参数字段。dlIMZh5jzCLjaFePUcXqKAODVjw+KvlmqOtoVYi2eV8=dlIMZh5jzCLjaFePUcXqKAODVjw+KvlmqOtoVYi2eV8= ![[DKnryelQBuRw8OiM-3a69e81b.webp]] 把所有和接口信息相关的地方补充上请求参数。 ![[sj5nvgi1P7LWOrmu-041bf3a0.webp]] ![[dAAdv01rgHUAx0kH-27561baa.webp]] ![[ywMlXlGOYdVVZmCh-b70cfd42.webp]] ![[F3VbP7OnBeQ1VGtE-fc72431a.webp]] 重启后端项目。 ![[Ps6ObWqoyEWTvhYN-cf836e2d.png]] 回到前端,执行 openapi,重新生成。8t5+VJlIX2Lzofl38AYV5bnrc6jCeMh9kFLnSW4W2fY=sZkzRi3MTD+un8ei2gjakRryTSmKof47t4VavacNfrE= ![[BHBbFCCL71CIlBap-d3d6b448.webp]] 在查看接口文档页补充请求参数。 ![[tqzCbzwAo1ZzBFXu-4952516a.webp]] 修改列的定义。 ![[uByjtT6x5NaLBlch-94a18953.webp]] 把请求头和响应头的 textarea 改为 jsonCode。y0KKJH83GHzBF93m63zbbBynhjavwWswKIbe+8lmuns=MHQxthdjzFhIWjS4Nn+sBZ3SJfic4DfvRwDIcKLFTjo= ![[6HhkgfD6hVeEhk5E-d5c6b2a5.webp]] 回到前端页面,点击`修改`。 ![[9ucg5FtqE6UV0ADh-5dfb2fb1.webp]] 填写请求参数,我们怎么知道接收什么参数?找到这个接口。 ![[8O5p8COKqDDxJvIt-9795a184.webp]] 它接收的是 User,光标放到 User 上,按[Ctrl+鼠标左键]查看。I/mv7JqPu0KdBgBsR4rkFWPOT/qJ5Yd7ZLlmeKodCQ4=QFU7mhNWUEojH1P/mbZkTNJwHPK/zMECeOY+OMwV/3U= ![[vjpAD1H2KpkwdDie-630b58fd.webp]] 那参数就很简单了,但是这里有个问题,我们这里**请求参数难道只写 username 吗?** 是不是还有一个类型,所以这里还要约定一个类型,类型的约定交给大家实现。MHQxthdjzFhIWjS4Nn+sBZ3SJfic4DfvRwDIcKLFTjo=I/mv7JqPu0KdBgBsR4rkFWPOT/qJ5Yd7ZLlmeKodCQ4= --- 💡 在线调用中有一个**关键点**,那就是确定前端向后端发送请求时所需的一些信息,例如请求参数的类型。在开始开发之前,建议大家先不要写代码,而是先思考一下。比如,如果在 Java 中使用了 String 类型,那前端对应的请求参数应该是什么类型呢?这里不用自己定义,因为我们都是用 JSON 来作为请求参数的类型,直接使用 JSON 类型即可。 可以搜一下 JSON 的基本类型。y0KKJH83GHzBF93m63zbbBynhjavwWswKIbe+8lmuns=I/mv7JqPu0KdBgBsR4rkFWPOT/qJ5Yd7ZLlmeKodCQ4= ![[XelRcYhsu7BIr4DB-2c1e2c77.webp]] --- 请求参数的类型(直接用 json 类型,更灵活):nxygd+ImutUjNSlTu1uMISbMTMWPTt4PJRfqBmRyZn8=j3nSzs+NO5SqQKOeNTdarf4VPxB+8MbZdVOU86YsPf4= ```json [ {"name": "username", "type": "string"} ] ``` 如果大家在企业中写文档的话,把它写到注释里,或者说写一个例子、实例; 可能会收获同事的感谢🐶MHQxthdjzFhIWjS4Nn+sBZ3SJfic4DfvRwDIcKLFTjo=y0KKJH83GHzBF93m63zbbBynhjavwWswKIbe+8lmuns= ![[RbZK8oLZDvwcX5jL-303bf77b.webp]] 粘贴到请求参数框中。 ![[pyWbB1NZ5f6cprOU-7304cc4b.webp]] 操作成功,也自动格式化了**(解决了修改按钮 bug 可跳过 01:32:16-01:39:53)**。 ps.如果操作失败,提示 id 为 null,可见上期教程:**六、前端项目开发 → 2.实现新建功能** 的 **修改操作演示**。 ![[NYbuC8r0fPKDFb0b-2bd60b7c.webp]] --- 💡 **由操作错误引出的问题:**当出现像这样的参数传递错误时,我们应该进行一些反思和考虑。对于这种请求头、响应头等信息,我们可以在表单的设计上下功夫。可以将表单改造成每个请求参数单独一行让用户输入,每个请求头也单独一行,然后将这个 JavaScript 对象转换为 JSON 格式,再传递给后台,这样就不会出现换行符等问题了,这有点类似于之前提到的 SQLFather 项目中的表单设计。通过这种方式,我们能够更清晰地管理请求参数和请求头,确保它们的准确性和一致性。 ![[fHLQ96kgkakVxPn5-c4dd1210.webp]] ## 八、开发在线调用(时间点 01:40:20-02:15:07) ### 1. 前端增加调用按钮(时间点 01:40:20-01:47:30) 模拟假接口的数据就造好了,现在我们来在线调用,在查看接口文档右上角添加调用按钮。sZkzRi3MTD+un8ei2gjakRryTSmKof47t4VavacNfrE=y0KKJH83GHzBF93m63zbbBynhjavwWswKIbe+8lmuns= ![[gmMUtFP8Ksmo6jE9-164a11d7.webp]] 这里可以仿造 Swagger 文档,只不过我们已经有信息了; 只需要多加一个输入框,用来输入请求参数,还有一个发送按钮。ZQrfE/sCxk5l2HPzrk0ji4jR2Ph837ekIgMbdJHkyJI=ZQrfE/sCxk5l2HPzrk0ji4jR2Ph837ekIgMbdJHkyJI= ![[YamNTOseZWuTzuck-5d3a3ad4.webp]] 在查看接口文档页的描述列表下新加一个卡片。 ![[57CfLCn7Pua5tiOj-8b304810.webp]] [官网](https://ant.design/components/form-cn) 找一个现成表单。 ![[euPPsYjp2pKmjKjj-d60e36f5.webp]] 复制表单代码。MHQxthdjzFhIWjS4Nn+sBZ3SJfic4DfvRwDIcKLFTjo=nxygd+ImutUjNSlTu1uMISbMTMWPTt4PJRfqBmRyZn8= ```html
Remember me
``` 粘贴到卡片里。 ![[RvNHpPArw3e7LLp3-c6271349.webp]] 删除表单多余的内容。 ![[m6zo26xIGjYmWgJh-c3863bc1.webp]] ```text