笔记
# 知识点
# 1. API 开放与接口开发的区别
"API 开放"和"接口开发"是两个不同的概念。
- API(Application Programming Interface)指的是应用程序编程接口,它是一种用于不同软件系统之间互相通信的技术规范。在软件开发中,API 可以让开发者在不了解底层实现细节的情况下,使用某个软件系统的功能。
- API 开放通常指的是将某个软件系统的 API 接口对外公开,以便其他开发者或系统可以使用该系统的功能。例如,一个社交媒体应用可以开放其 API 接口,以便第三方开发者可以通过 API 调用来访问该应用的用户数据或其他功能。
- 接口开发则是指在软件开发中,开发人员编写代码来实现 API 接口。这些接口可以是用于不同系统之间通信的 RESTful API,也可以是用于不同模块之间通信的内部 API。在接口开发中,开发人员需要定义接口的输入输出参数、处理逻辑、异常处理等细节。
"API 开放"是一种用于将某个软件系统的功能对外公开的技术手段,而"接口开发"是一种用于实现 API 接口的技术实践。
# 2. API 网关与微服务网关的区别
"API 网关"和"微服务网关"是两个不同的概念,虽然它们都是用于构建分布式系统的核心组件,但它们的功能和作用有所不同。
| 对比项 | API 网关 | 微服务网关 |
|---|---|---|
| 管理对象 | API 请求 | 微服务请求 |
| 核心功能 | 路由、负载均衡、缓存、认证、授权、限流 | 路由、负载均衡、服务发现、熔断 |
| 应用场景 | 微服务架构、云原生应用、RESTful API | 微服务架构、服务治理、容错机制 |
API 网关管理的是 API 请求,而微服务网关管理的是微服务请求。虽然它们的功能有所重叠,但在实际应用中需要根据场景和需求选择合适的网关组件。
# 3. 什么是 npm 和 Node.js
npm 和 Node.js 是两个不同的概念,但它们通常是一起使用的。
- Node.js 是一种基于 Chrome V8 引擎的 JavaScript 运行环境,它可以让 JavaScript 在服务器端运行,同时还提供了许多方便的 API 和模块,让开发者能够快速构建高性能的网络应用程序。Node.js 还具有事件驱动、非阻塞 I/O 等特点,能够处理大量并发请求。
- npm(Node Package Manager)是 Node.js 的包管理器,它是一个用于共享、发布、安装和管理 Node.js 模块的命令行工具。在 npm 上,开发者可以搜索、查看和下载各种 Node.js 模块,同时也可以发布自己编写的模块供其他开发者使用。
通常情况下,开发者在使用 Node.js 时会使用 npm 来管理所需的模块和依赖。例如,在一个 Node.js 项目中,我们可以使用 npm 安装第三方模块:
npm install express
npm install react
2
npm 会自动下载和安装所需的模块及其依赖,并将它们保存在项目的 node_modules 目录下。在代码中,我们可以使用 require 或 import 语句来引入所需的模块:
const express = require('express');
import React from 'react';
2
Node.js 和 npm 是一对不可分割的工具,它们为 JavaScript 开发者提供了方便、高效的开发环境和工具。
# 4. Spring Boot 中 servlet.context-path 配置的作用
server:
port: 7529
servlet:
context-path: /api
2
3
4
这个配置用于指定 Servlet 的上下文路径。Servlet 的上下文路径默认为 /,也就是应用的根路径,但你可以通过修改这个配置项来改变 Servlet 的上下文路径。例如,将上下文路径配置为 /api,那么应用的根路径就变成了 http://localhost:7529/api/。
具体用途包括:
- 控制 Web 应用的根路径:通过修改上下文路径,可以改变 Web 应用访问时的 URL 路径。
- 避免 URL 冲突:不同的 Web 应用可能使用相同的 URL 路径,通过修改上下文路径可以避免冲突。
- 提高安全性:修改上下文路径可以隐藏 Web 应用的真实路径,降低被攻击的风险。
# 5. Spring MVC 中 pathmatch.matching-strategy 配置
spring:
mvc:
pathmatch:
matching-strategy: ANT_PATH_MATCHER
2
3
4
这个配置用于指定 URL 路径匹配的策略。在 Spring MVC 中,URL 路径的匹配是通过 AntPathMatcher 类来实现的,这个类可以使用 Ant 风格的通配符进行路径匹配,比如 /user/*、/user/**、/user/{id} 等。
具体用途包括:
- 指定 URL 路径匹配策略:默认情况下,Spring MVC 使用
AntPathMatcher进行匹配,也可以使用其他的匹配策略,比如正则表达式匹配。 - 控制 URL 路径匹配的粒度:比如使用
/user/*匹配所有以/user/开头的路径,或使用/user/{id}匹配指定 ID 的路径。 - 提高 URL 路径匹配的效率:在大规模的 Web 应用中,通过选择合适的匹配策略可以提高匹配效率。
# 6. npm install 和 npm run install 的区别
在 npm 中,npm install 和 npm run install 是有区别的:
| 命令 | 作用 | 说明 |
|---|---|---|
npm install | 安装项目依赖包 | 查找 package.json 文件,根据 dependencies 和 devDependencies 字段安装依赖包 |
npm run install | 运行名为 install 的脚本 | 在 package.json 的 scripts 字段中定义的脚本 |
npm install:如果没有指定特定的依赖包名称,则默认会安装package.json中列出的所有依赖包。例如,npm install安装所有依赖,npm install express仅安装express。npm run install:用于运行package.json的scripts字段中定义的名为install的脚本。
# 7. 前端脚手架 Ant Design Pro
# 7.1 Ant Design 是什么?属于 React 吗
Ant Design 是一个基于 React 的 UI 组件库,它提供了大量的高质量组件和模板,可以帮助开发者快速构建美观、易用、高效的 Web 界面。因为 Ant Design 是基于 React 开发的,所以它可以轻松地与 React 应用程序集成。但是,Ant Design 并不仅限于 React,它也可以与其他前端框架集成,例如 Vue 和 Angular。
# 7.2 Ant Design Pro
Ant Design Pro 是一个开箱即用的企业级前端应用程序解决方案,它基于 Ant Design 和 React 技术栈,提供了丰富的业务组件和模板,帮助开发者快速构建出高质量、高效的管理系统、后台应用程序等。
Ant Design Pro 提供了许多实用的工具和功能,例如国际化、权限管理、数据可视化、代码生成等,可以大大提高开发效率和质量。Ant Design Pro 是一个完全开源的项目,可以免费使用和自由修改。
# 7.3 Ant Design Pro 与 Vue 的选型
| 对比项 | Ant Design Pro | Vue |
|---|---|---|
| 技术栈 | 基于 React | 渐进式 JavaScript 框架 |
| 适用场景 | 快速开发、企业级应用 | 轻量级开发、单页面解决方案 |
| 类似方案 | — | Element UI Pro |
如果您的团队熟悉 React 并且需要一个功能丰富的解决方案,Ant Design Pro 是不错的选择;如果您更熟悉 Vue 并且需要轻量级解决方案,可以考虑 Element UI Pro。
# 8. 为什么要使用 DTO 包
DTO(Data Transfer Object)是一种用于在不同层次或模块之间传递数据的对象,它通常包含了需要在不同层次或模块之间传递的数据和相关的元数据。DTO 的使用可以带来以下好处:
- 降低耦合度:将数据传输的逻辑从业务逻辑中分离出来,降低不同模块或层次之间的耦合度,便于代码的维护和升级。
- 提高性能:减少不必要的数据传输,提高系统的性能和响应速度。例如,在远程调用或网络传输中,使用
DTO可以只传输需要的数据,而不是传输整个对象或实体。 - 简化接口设计:将复杂的数据结构转换为简单的数据对象,简化接口的设计和维护。例如,在前后端分离的应用中,使用
DTO可以将后端返回的数据转换为前端需要的数据结构。 - 提高安全性:避免将敏感数据直接传输到客户端或其他不安全的环境中。例如,在 Web 应用中,使用
DTO可以将用户输入的数据转换为安全的数据结构,避免 SQL 注入、XSS 等安全问题。
# 9. @PathVariable 和 @RequestParam
@PathVariable 和 @RequestParam 都是 Spring MVC 中用于获取请求参数的注解,但它们的使用场景和获取方式有所不同。
| 注解 | 获取方式 | 使用场景 | 示例 URL |
|---|---|---|---|
@PathVariable | URL 路径中的参数值 | RESTful 风格接口 | http://localhost:8080/user/123 |
@RequestParam | URL 中的查询参数 | ?key=value 形式的参数 | http://localhost:8080/user?id=123 |
@PathVariable 示例:
@GetMapping("/user/{id}")
public User getUserById(@PathVariable("id") Long id) {
// ...
}
2
3
4
@RequestParam 示例:
@GetMapping("/user")
public User getUserById(@RequestParam("id") Long id) {
// ...
}
2
3
4
# 10. synchronized (userAccount.intern())
synchronized 是 Java 中用来控制多线程访问共享资源的关键字。它可以修饰方法和代码块,当被修饰的方法或代码块被执行时,只有一个线程可以进入到方法或代码块中执行,其他线程需要等待该线程执行完毕后才能进入。
在这段代码中,synchronized 修饰的是 userAccount.intern()。intern() 是 Java 中的一个方法,它可以返回字符串对象的一个实例:如果内存中已存在该字符串,则返回已存在的实例;如果不存在,则创建一个新的实例。
使用
synchronized可以保证同一时间只会有一个线程进入到这段代码中,避免多个线程同时修改userAccount导致数据不一致的问题。
# 11. @TableField(exist = false) 和 serialVersionUID
@TableField(exist = false)
private static final long serialVersionUID = 1L;
2
这段代码是 Java 语言中使用 MyBatis-Plus 框架时,用于实体类字段的注解:
@TableField:表示该注解用于标识实体类对应的数据库表字段。exist = false:表示该字段不是数据库表中的一列,即该字段不对应数据库中的实际列。serialVersionUID:Java 中的特殊变量,用于在序列化和反序列化对象时标识对象的版本,以保证对象的兼容性。在实体类中声明serialVersionUID可以防止序列化和反序列化时出现版本不一致的问题。
# 12. @EqualsAndHashCode(callSuper = true)
这是 Java 语言中使用 Lombok 框架时,用于生成 equals 和 hashCode 方法的注解:
@EqualsAndHashCode:表示该注解用于生成equals和hashCode方法。callSuper = true:表示在生成的equals和hashCode方法中,将会调用父类的equals和hashCode方法,以确保子类与父类的对象比较时也能正确比较父类中的字段。
通过在类上添加
@EqualsAndHashCode(callSuper = true)注解,可以简化开发人员编写equals和hashCode方法的过程,提高开发效率。
# 13. 断言有什么用
断言是一种用于在代码中检查假设条件的机制。它可以帮助开发人员在编写代码时检查程序是否满足了预期的条件,如果条件不满足,则会引发 AssertionError 异常,从而提高了代码的健壮性和可靠性。
断言的使用场景:
- 调试和测试:在开发过程中检查函数参数的有效性、对象的状态等假设条件。
- 错误检测和容错:在编写高可靠性的系统时,检查系统的状态是否处于预期状态,如果发现状态异常,则可以采取相应的措施(如恢复系统或报警通知操作人员)。
# 14. GlobalFilter 全局请求拦截处理
使用了 GlobalFilter(编程式),全局请求拦截处理(类似 AOP)。
这句话指的是在一个 Web 应用程序中,使用了一种名为 GlobalFilter 的编程式拦截器来对全局的请求进行拦截和处理。
- GlobalFilter 是 Spring WebFlux 框架提供的一种全局过滤器,它类似于 AOP(面向切面编程)的思想,可以在请求处理的前后、异常处理等各个环节进行拦截和处理。使用
GlobalFilter可以方便地对请求进行统一的预处理、鉴权、日志记录等操作,而无需在每个请求处理方法中单独处理。 - 编程式(Programmatic)指的是以编程的方式实现某种功能或处理某个问题,而不是通过配置或其他方式来实现。相对于声明式编程(Declarative),它更加灵活,可以更加精细地控制程序行为,但需要更多的代码维护和处理。
# 15. 装饰者模式
装饰者模式是指在不改变原有对象的基础上,动态地给一个对象添加一些额外的职责,就增加功能来说,装饰者模式比生成子类更为灵活。
装饰者模式的主要角色:
| 角色 | 说明 |
|---|---|
| 抽象构件(Component) | 定义了抽象接口,为装饰者和被装饰者对象声明了公共接口 |
| 具体构件(ConcreteComponent) | 是被装饰者,实现了抽象构件的接口 |
| 抽象装饰器(Decorator) | 维护一个指向抽象构件对象的引用,并定义了与抽象构件一致的接口 |
| 具体装饰器(ConcreteDecorator) | 是抽象装饰器的子类,负责给具体构件对象添加一些职责 |
装饰者模式的实现方式是通过将一个对象包装到另一个对象中,从而形成一条装饰链。当需要增加新的行为时,只需要添加新的具体装饰器即可,而不需要修改原有的代码。
# 16. 什么是魔法值
在 Java 中,魔法值指的是代码中出现的一些不具备明确含义的常量值。这些常量值通常以硬编码的方式出现在代码中,而没有给出明确的说明或注释。
例如,以下代码中的数字常量就是魔法值:
if (statusCode == 200) {
// do something
}
2
3
数字常量 200 并没有明确的含义。如果将它定义为一个常量,就可以提高代码的可读性和可维护性:
private static final int HTTP_OK = 200;
if (statusCode == HTTP_OK) {
// do something
}
2
3
4
在编写 Java 代码时,尽可能避免使用魔法值,而是使用常量来代替。
# 17. 什么是 RPC
RPC(Remote Procedure Call)是一种远程过程调用协议,它允许不同的应用程序在分布式系统中通过网络相互调用,就像本地调用一样。
RPC 的基本思想是:客户端像调用本地函数一样,通过传递参数调用远程服务,然后等待远程服务返回结果。RPC 框架负责将客户端的请求转换成网络传输的消息,然后将消息发送给服务端,服务端接收到消息后进行处理,并将处理结果返回给客户端。
| 优点 | 缺点 |
|---|---|
| 使得分布式应用程序的编写更加简单和高效 | 需要设计和实现通信协议、序列化、反序列化、错误处理等细节 |
| 客户端和服务端可以使用不同的编程语言和框架 | 需要考虑网络故障、服务端负载均衡等问题 |
| 可以将应用程序的不同部分分布在不同的机器上 | 性能会受到网络延迟和带宽等因素的影响 |
| 提高了应用程序的可伸缩性和可靠性 | 实现起来比较复杂 |
# 18. model 包、dto 包和 vo 包的分析
在 Java 的 Web 开发中,通常会将数据模型(Model)按照功能和特性分成不同的包:
| 包名 | 全称 | 说明 |
|---|---|---|
| model | — | 业务领域中的核心数据模型(领域模型),与数据库中的表结构相对应,用于数据的持久化和业务逻辑的处理 |
| dto | Data Transfer Object | 数据传输对象,用于在应用程序的不同层之间传输数据,不包含业务逻辑 |
| vo | Value Object | 值对象,用于向客户端展示数据,通常会将属性进行格式化或组合,便于界面显示,属性通常是只读的 |
# 19. 输出日志而不是抛异常的原因
try {
innerUserInterfaceInfoService.invokeInterfaceCount(userId, interfaceInfoId);
} catch (Exception e) {
log.error("invokeInterfaceCount error", e);
}
2
3
4
5
在这段代码中,捕获了 invokeInterfaceCount() 方法的异常,并使用日志输出了异常信息。这种处理方式被称作异常屏蔽(Exception Masking),即将异常捕获并处理,而不是将异常继续抛出。
异常屏蔽的目的是为了防止异常传递到上层调用者,并影响程序的正常执行。由于 invokeInterfaceCount() 方法是一个远程调用,可能会存在网络故障、服务不可用等异常情况,这些异常如果直接抛出,会导致整个请求无法正常响应,从而影响用户体验。
使用日志输出异常信息,可以帮助开发人员及时了解异常情况,以便问题排查和修复。同时,程序也可以继续执行其他逻辑,提高了程序的健壮性和可靠性。需要注意的是,如果异常是无法处理的,或者需要立即通知上层调用者,应当将异常重新抛出。
# 遇到的难题
# 1. TypeError: this.libOptions.parse is not a function
解决方法:执行以下命令安装 ESLint 8.22 版本:
npm install eslint@8.22.0 --save-exact
# 2. 查询表格页为英文
这是因为删除了初始化的国际化文件导致的。

解决方法:修改 src/pages/TableList/index.tsx 文件,更改 title。
# 3. 查询分页数据有误


解决方法:开启 MyBatis-Plus 的驼峰映射。

原因:sql 语句是 IService 内的方法,是属于 MyBatis-Plus 的,MyBatis 的 xml 文件映射其实没有生效。
# 4. 数据无法加载

原因:修改文件的时候不小心把 interfaceInfoController 中的路径名也改了。
# 5. 添加接口数据时,请求参数有误

解决方法:取消这个判断。

# 6. 调用 Hutool 的 HTTP 工具类,测试报错

原因:未启动服务器。启动服务器之后即可运行成功。

# 7. 一直登录不成功

解决方法:要改这里,跳转的布局。
# 8. Nacos 注册中心没有信息,网关项目空指针
原因:backend 项目的启动类中没有导入 @EnableDubbo。
# 9. java.lang.IllegalStateException: Failed to check the status of the service
Failed to check the status of the service com.chenmeng.provider.DemoService.
No provider available for the service com.chenmeng.provider.DemoService
from the url consumer://192.168.194.1/com.chenmeng.provider.DemoService?...
2
3
原因:提供者和调用方的接口类 DemoService 所在的目录不是同样的名字下。

# 10. ClassCastException 类型转换异常
Map<Long, List<UserInterfaceInfo>> interfaceInfoIdObjMap = userInterfaceInfoList.stream()
.collect(Collectors.groupingBy(UserInterfaceInfo::getInterfaceInfoId));
2
报错:java.lang.ClassCastException: com.chenmeng.project.model.vo.InterfaceInfoVO cannot be cast to com.chenmeng.cmapicommon.model.entity.UserInterfaceInfo
解决方法:把 UserInterfaceInfo 改成 InterfaceInfoVO 即可解决。
# 11. 响应错误
原因:一步步 debug 发现 accessKey 测试的时候用了中文,出现了乱码,导致 response 响应出错。
# 12. 其他问题
(待补充)
# TODO
# 1. 追求性能的话可以注释,直接走缓存
该句话的意思是:如果追求性能,可以将从数据库查询用户信息的操作注释掉,直接从缓存中获取用户信息,以提高查询效率。
具体实现步骤:
- 在用户登录成功后,将用户信息存储到缓存中:
// 用户登录成功后,将用户信息存储到缓存中
redisTemplate.opsForValue().set(USER_LOGIN_STATE + currentUser.getId(), currentUser);
2
其中,redisTemplate 是 Spring 提供的与 Redis 交互的工具类,USER_LOGIN_STATE 是常量,表示存储在缓存中的键值前缀,currentUser.getId() 则是用户 ID,表示具体的键值。
- 修改获取当前登录用户信息的方法,先从缓存中获取用户信息,如果缓存中不存在则从数据库中查询,并将查询结果存储到缓存中:
public User getLoginUser(HttpServletRequest request) {
// 先从 Session 中获取用户信息
Object userObj = request.getSession().getAttribute(USER_LOGIN_STATE);
User currentUser = (User) userObj;
if (currentUser == null || currentUser.getId() == null) {
throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR);
}
// 从缓存中查询用户信息
String key = USER_LOGIN_STATE + currentUser.getId();
currentUser = redisTemplate.opsForValue().get(key);
if (currentUser == null) {
// 从数据库中查询用户信息
currentUser = this.getById(currentUser.getId());
if (currentUser == null) {
throw new BusinessException(ErrorCode.NOT_LOGIN_ERROR);
}
// 将查询结果存储到缓存中
redisTemplate.opsForValue().set(key, currentUser);
}
return currentUser;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
- 在使用缓存的场景下,需要考虑缓存与数据库的数据一致性问题,即当用户信息发生变更时,需要及时更新缓存:
public void updateUser(User user) {
// 更新数据库中的用户信息
this.updateById(user);
// 更新缓存中的用户信息
redisTemplate.opsForValue().set(USER_LOGIN_STATE + user.getId(), user);
}
2
3
4
5
6
# 2. 根据测试地址来调用模拟接口
利用反射来解决问题,核心代码如下:
public Object reflectionInterface(Class<?> reflectionClass, String methodName, String parameter, String accessKey, String secretKey) {
// 构造反射类的实例
Object result = null;
try {
Constructor<?> constructor = reflectionClass.getDeclaredConstructor(String.class, String.class);
// 获取 SDK 的实例,同时传入密钥
CmApiClient tempClient = (CmApiClient) constructor.newInstance(accessKey, secretKey);
// 获取 SDK 中所有的方法
Method[] methods = tempClient.getClass().getMethods();
// 筛选出调用方法
for (Method method : methods) {
if (method.getName().equals(methodName)) {
// 获取方法参数类型
Class<?>[] parameterTypes = method.getParameterTypes();
Method method1;
if (parameterTypes.length == 0) {
method1 = tempClient.getClass().getMethod(methodName);
return method1.invoke(tempClient);
}
method1 = tempClient.getClass().getMethod(methodName, parameterTypes[0]);
// getMethod,多参会考虑重载情况获取方法,前端传来参数是 JSON 格式转换为 String 类型
// 参数 JSON 化
Gson gson = new Gson();
Object args = gson.fromJson(parameter, parameterTypes[0]);
return result = method1.invoke(tempClient, args);
}
}
} catch (Exception e) {
log.error("反射调用参数错误", e);
}
return result;
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
这段代码通过反射机制,根据传入的类名和方法名,实例化相应的对象,调用相应的方法,并将参数传递给方法执行,最后返回结果。它可以灵活地调用不同类的方法,适用于一些动态性较强的场景,但需要注意反射调用的性能和安全性。