配置
IDEA:2024.03
JDK:11
虚拟机:VMware® Workstation 17 Pro
Day01 初识项目
配置本机 host
在 Windows 系统中,你可以通过修改系统的 hosts 文件来实现域名到虚拟机 IP 地址和端口的映射。这是一个简单的操作,不需要任何第三方软件。以下是详细步骤:
步骤 1: 打开 Hosts 文件
-
打开记事本程序(Notepad)。
-
在记事本中,选择 “文件” > “打开”,或者直接按快捷键
Ctrl + O
。 -
在打开对话框中,输入
C:\Windows\System32\drivers\etc\hosts
,然后点击 “打开” 按钮。- 注意:你可能需要管理员权限来编辑这个文件。如果系统提示,选择 “是” 以允许对文件进行更改。
步骤 2: 添加域名映射
在打开的 hosts 文件中,添加一行新的文本,格式如下:
`192.168.150.101 yourdomain.com
- 将
yourdomain.com
替换为你想要映射的域名。
步骤 3: 刷新 DNS 缓存(可选)
为了确保你的更改立即生效,可以刷新 DNS 缓存。打开命令提示符(以管理员身份运行),然后输入以下命令:
ipconfig /flushdns
按回车键执行命令。
文件权限配置
在 jenkins 运行某个微服务时(例如 tj-gateway),会构建失败
Started by user [huge](http://jenkins.tianji.com/user/root)
Running as SYSTEM
Building in workspace /var/jenkins_home/workspace/tj-gateway
[tj-gateway] $ /bin/sh -xe /tmp/jenkins11280295088725351975.sh
+ ssh root@192.168.150.101 /usr/local/src/script/startup.sh -c tj-gateway -n tj-gateway -d tj-gateway -p 10010
copy xx.jar from /usr/local/src/jenkins/workspace/tjxt-dev-build/tj-gateway
cp: cannot stat '/usr/local/src/jenkins/workspace/tjxt-dev-build/tj-gateway/target/tj-gateway.jar': No such file or directory
Build step 'Execute shell' marked build as failure
Finished: FAILURE
观察上面的错误,构建过程是在读取 gateway.jar
时失败的,在虚拟机查看相应目录的权限:
src
目录没有读取权限,使用 chmod src 777
后即可构建成功。
项目本地部署
整个项目 install 出错,找不到符号 log
更改 JDK 的版本为 11
IDEA 本地部署报错
更改 JDK 版本为 11 后,整个项目构建过程中 tj-course 报错,但 tj-exam 不报错:
[WARNING] The POM for com.alibaba:druid:jar:1.2.6 is invalid, transitive dependencies (if any) will not be available: 2 problems were encountered while building the effective model for com.alibaba:druid:1.2.6
[ERROR] 'dependencies.dependency.systemPath' for com.sun:tools:jar must specify an absolute path but is ${project.basedir}/lib/openjdk-1.8-tools.jar @
[ERROR] 'dependencies.dependency.systemPath' for com.sun:jconsole:jar must specify an absolute path but is ${project.basedir}/lib/openjdk-1.8-jconsole.jar @
但是项目显示构建成功
[INFO] tj-course .......................................... SUCCESS [ 16.096 s]
此时添加排除依赖后 ExamApplication 可以正常运行(需要先把 JDK 版本改为 11!!!)
<dependency>
<artifactId>seata-spring-boot-starter</artifactId>
<groupId>io.seata</groupId>
<version>${seata-version}</version>
<exclusions>
<exclusion>
<groupId>com.sun</groupId>
<artifactId>tools</artifactId>
</exclusion>
<exclusion>
<groupId>com.sun</groupId>
<artifactId>jconsole</artifactId>
</exclusion>
</exclusions>
</dependency>
修改 NACOS 实例权重或者对某实例下线报错
在 Nacos 控制台进行上述操作,错误信息
caused: errCode: 500, errMsg: do metadata operation failed ;caused: com.alibaba.nacos.consistency.exception.ConsistencyException:
The Raft Group [naming_instance_metadata] did not find the Leader node;caused: The Raft Group [naming_instance_metadata] did not
find the Leader node;
原因:Nacos 采用 raft 算法来计算 Leader, 并且会记录上次启动的集群地址,所以当我们自己的服务器 IP 改变时(网络环境不稳定,如 WIFI,IP 地址也经常变化),导致 raft 记录的集群地址失效,导致选 Leader 出现问题,
解决方法:删除 Nacos 根目录下 data 文件夹下的 protocol 文件夹,重启 nacos 即可
#相关命令
#进入nacos容器
docker exec -it nacos /bin/bash
#进入nacos的data文件
cd data
#删除protocol文件夹
rm -rf protocol/
#退出容器
exit
#重启nacos容器
docker restart nacos
电脑重启后无法访问虚拟机地址
重启网络和 Docker
systemctl restart networking
sudo systemctl restart docker
然后可以通过 ip+port 访问到 nacos,jenkins 等等,host 文件配置也正确,但就是用域名无法访问,后面重启了 nginx 就行了
sudo systemctl restart nginx
可能是因为 DNS 缓存或者端口冲突问题,重启都能解决
Day02 我的课表
IDEA 配置 MybatisPlus 生成代码
教程上在 Other 下进行配置:
IDEA 更新后,在 Tools 下配置:
MyBatis-Plus 的批量插入方法 saveBatch()
在实现添加课程到课表时,在获取到相应的课表信息后,使用 saveBatch() 方法批量新增数据
saveBatch(list);
在这个过程中并没有调用 Mapper 层的方法,其实现原理如下:
在 MyBatis-Plus 中,saveBatch() 方法是一个批量插入操作,它能知道要保存到哪个数据表,主要依赖于以下几个方面:
1. **实体类的映射信息**
- 每个实体类通常会通过注解(例如 @TableName)或配置来指定对应的数据库表名,以及各个字段与数据库列之间的映射关系。
- 当你调用 saveBatch() 并传入一个实体列表时,框架会通过反射读取这些元数据,从而确定目标数据表及列信息。
2. **Mapper 与 Service 的绑定**
- MyBatis-Plus 的 ServiceImpl 类内部会注入 BaseMapper,该 Mapper 是针对特定实体类生成的。BaseMapper 内部包含了自动生成的 CRUD 操作,保存操作自然会使用该实体对应的表信息。
- saveBatch() 实际上就是调用 BaseMapper 的批量插入方法,利用实体类的元数据生成相应的 INSERT 语句。
3. **底层实现**
- 底层通过反射和 SQL 生成器,MyBatis-Plus 根据实体类上的注解和配置构造出完整的 INSERT SQL。
- 然后利用 MyBatis 的批量执行器(Batch Executor)将多条 INSERT 语句一起执行,以提高性能和减少数据库交互次数。
总的来说,saveBatch() 方法之所以能知道保存到哪个表,完全依赖于实体类的映射信息和框架自动生成的 Mapper 配置。这样做不仅简化了批量插入的流程,还减少了手写 SQL 和出错的风险,同时也便于维护和扩展。
添加课程到课表本地测试
服务启动后,在控制台一直没有显示 MQ 发送的消息,然后查看请求的接口
且报错 服务不存在
。
根据 api 分析出请求的是 tj-trade 服务,重新打开即可(之前测试时忘记重新打开了)
然后在数据库中更改《Java 泛型》课程的截至报名时间后即可报名成功!
从课表删除课程
删除课程上使用 QueryMapper
报错 String is not a functional interface
int count = lessonMapper.delete(new QueryWrapper<LearningLesson>()
.eq(LearningLesson::getUserId, userId)
.in(LearningLesson::getCourseId, courseIds));
因为前面创建对象的时候使用的是 QueryWrapper 但是你在使用用却使用了 lambda 表达式,他就会把表达式识别为字符串 (就会提示字符串不是一个功能接口)。
把 QueryMapper
改为 LambdaQueryWrapper
即可。
Day03 学习计划和进度
Spring Task 定时任务
这里有一个需求是:编写一个 Spring Task 定时任务,定期检查 learning_lesson 表中的课程是否过期,如果过期则将课程状态修改为已过期。
- 在
LearningApplication
加上@EnableScheduling
开启定时任务功能 - 在具体的定时任务函数上加上
@Scheduled
并配置 cron 表达式即可
Spring Task 的缺点:
- 默认单线程执行
- 默认不支持分布式(使用持分布式的定时任务调度框架,比如 Quartz、XXL-Job 等)
Day05 问答系统
Spring 启动报错循环依赖
启动 LearningApplication 报错(创建许多 bean 时):
Exception encountered during context initialization - cancelling refresh attempt: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'interactionQuestionAdminController' defined in file [D:\Workspace\SpringProject\tianji\tj-learning\target\classes\com\tianji\learning\controller\InteractionQuestionAdminController.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.
UnsatisfiedDependencyException: Error creating bean with name 'interactionQuestionServiceImpl' defined in file [D:\Workspace\SpringProject\tianji\tj-learning\target\classes\com\tianji\learning\service\impl\InteractionQuestionServiceImpl.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.
UnsatisfiedDependencyException: Error creating bean with name 'interactionReplyServiceImpl' defined in file [D:\Workspace\SpringProject\tianji\tj-learning\target\classes\com\tianji\learning\service\impl\InteractionReplyServiceImpl.class]: Unsatisfied dependency expressed through constructor parameter 0; nested exception is org.springframework.beans.factory.BeanCurrentlyInCreationException: Error creating bean with name 'interactionQuestionServiceImpl': Requested bean is currently in creation: Is there an unresolvable circular reference
根据报错分析,是使用了循环依赖,导致了创建 bean 时发生错误。
定位 InteractionQuestionServiceImpl
和 InteractionReplyServiceImpl
public class InteractionQuestionServiceImpl extends ServiceImpl<InteractionQuestionMapper, InteractionQuestion> implements IInteractionQuestionService {
private final IInteractionReplyService replyService;
...
}
public class InteractionReplyServiceImpl extends ServiceImpl<InteractionReplyMapper, InteractionReply> implements IInteractionReplyService {
private final IInteractionQuestionService questionService;
...
}
观察到,上面两个 Service bean 循环依赖,导致运行失败,把 IInteractionQuestionService
修改为 InteractionQuestionMapper
即可运行成功。
用户端查询报错
控制台报错如下:
ERROR 27080 --- [nio-8090-exec-5] c.t.c.a.m.advice.CommonExceptionAdvice : 其他异常 uri : /questions/page -> org.springframework.http.converter.HttpMessageNotReadableException: Required request body is missing: public com.tianji.common.domain.dto.PageDTO<com.tianji.learning.domain.vo.QuestionVO> com.tianji.learning.controller.InteractionQuestionController.queryQuestions (com.tianji.learning.domain.query.QuestionPageQuery) at org.springframework.web.servlet.mvc.method.annotation.RequestResponseBodyMethodProcessor.readWithMessageConverters (RequestResponseBodyMethodProcessor.java:163)
代码为
@ApiOperation("用户端分页查询")
@GetMapping("/page")
public PageDTO<QuestionVO> queryQuestions(@RequestBody QuestionPageQuery query) {
return questionService.queryQuestions(query);
}
@RequestBody
注解通常用于处理 POST 请求的请求体。GET 请求一般不包含请求体,因此会导致 “Required request body is missing” 的错误。去掉 @RequestBody
即可。
Day06 点赞系统
我想的是建立点赞和回答、评论等的关系表,这种做法的缺点是耦合度高,每次新增一个需要使用点赞功能的模块,就需要新建点赞关系表。
点没点赞是存储在 Redis 缓存的,只有具体业务的点赞次数会存在数据表中,由业务端维护。
Day08 排行榜
Xxl-Job 执行创建历史榜单表
任务执行失败,报错:
任务触发类型:手动触发 调度机器:172.18.0.6 执行器 - 注册方式:自动注册 执行器 - 地址列表:null 路由策略:轮询 阻塞处理策略:单机串行 任务超时时间:10 失败重试次数:3 >>>>>>>>>>> 触发调度 <<<<<<<<<<< 调度失败:执行器地址为空
执行器地址应该是启动 LearningApplication
后会自动注册,但现在正常启动后没有注册
![[Java 项目-8.png]]
![[Java 项目-10.png|373]]
但在 nacos 上是没问题的
![[Java 项目-9.png]]
本地启动 TradeApplication 后,Xxl-job 中 tarde-service 的注册节点数目也增加到了 2
![[Java 项目-11.png]]
LearningApplication 启动报错:
09:58:54.282-[DESKTOP-RJ5DTTM][sys] - WARN 53124 --- [ main] c.x.j.core.thread.TriggerCallbackThread : >>>>>>>>>>> xxl-job, executor callback config fail, adminAddresses is null.
09:58:54.284-[DESKTOP-RJ5DTTM][sys] - WARN 53124 --- [ main] c.xxl.job.core.executor.XxlJobExecutor : >>>>>>>>>>> xxl-job accessToken is empty. To ensure system security, please set the accessToken.
09:58:54.318-[DESKTOP-RJ5DTTM][sys] - INFO 53124 --- [ Thread-9] com.xxl.job.core.server.EmbedServer : >>>>>>>>>>> xxl-job remoting server start success, nettype = class com.xxl.job.core.server.EmbedServer, port = 9999
09:58:54.320-[DESKTOP-RJ5DTTM][sys] - WARN 53124 --- [ Thread-9] c.x.j.c.thread.ExecutorRegistryThread : >>>>>>>>>>> xxl-job, executor registry config fail, appname is null.
解决:tj-learning 的 bookstrap.Yaml 没有引入 xxl-job 加入后执行器地址配置成功:
- dataId: shared-xxljob.yaml
refresh: false
Day09 优惠券管理
思考:
- 优惠券定时发放会设置开始时间和结束时间,例如开始时间设置好了,通过定时任务来修改发放状态,定时任务应该不能设置地太频繁吧?(例如每分钟一次,负载压力会不会很大?)
现在设置为 10 分钟,不知道实际工程中如何设置
@Transactional 自调用错误
@Override
@Async ("generateExchangeCodeExecutor")
public void asyncGenerateCode (Coupon coupon) {
...
saveBatch (list);
}
报错:
@Transactional 自调用 (实际上是目标对象内的方法调用目标对象的另一个方法) 在运行时不会导致实际的事务
原因:asyncGenerateCode
方法被 @Async
注解标记,这意味着它会在一个单独的线程中异步执行。然而,@Transactional
注解默认情况下不会对异步方法生效,因为异步方法的调用不会通过 Spring 的代理对象,而是直接调用目标对象的方法。
解决方法:使用 @Transactional
的传播属性,改为
@Transactional(propagation = Propagation.REQUIRED)
或者获取当前的动态代理对象,然后调用事务方法。
分页查询优惠券报错
SQL 语句为:
SELECT id, name, type, discount_type, specific, discount_value, threshold_amount, max_discount_amount, obtain_way, issue_begin_time, issue_end_time, term_days, term_begin_time, term_end_time, status, total_num, issue_num, used_num, user_limit, ext_param, create_time, update_time, creater, updater FROM coupon ORDER BY create_time DESC LIMIT 5;
报错:
/* SQL 错误(1064):You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near'specific, discount_value, threshold_amount, max_discount_amount, obtain_way, iss' at line 1 */
把 specific 改为 ` specific ` 就可以了
同时在代码中进行更改即可:
@ApiModelProperty(value = "是否限定作用范围,false:不限定,true:限定。默认false")
@TableField("`specific`")
private Boolean specific;
Day10 领取优惠券
Jmeter 打开文件错误
java.lang.NoClassDefFoundError: Could not initialize class org.apache.jmeter.gui.util.FileDialoger ...
Caused by: java.lang.ExceptionInInitializerError: Exception java.lang.IllegalAccessError: class com.github.weisj.darklaf.ui.filechooser.DarkFilePaneUIBridge$DetailsTableModel...
把外观改为 window
即可,在暗黑模式下出问题。