天机学堂踩坑笔记

相关资源链接:
Md笔记:蓝奏云地址
在线笔记:飞书笔记地址
相关视频教程及配套课件:
链接:百度云地址
提取码:hmz1

1. Day01 初识项目

1.1 OpenEuler 22.03LTS yum换源失败

适用于OpenEuler版本为22.03LTS,需要指定具体版本。

yum换源,备份源文件并更新,文件位置:/etc/yum.repos.d/openEuler.repo

[OS]
name=OS
baseurl=https://repo.huaweicloud.com/openeuler/openEuler-22.03-LTS/OS/$basearch/
enabled=1
gpgcheck=1
gpgkey=https://repo.huaweicloud.com/openeuler/openEuler-22.03-LTS/OS/$basearch/RPM-GPG-KEY-openEuler[everything]
name=everything
baseurl=https://repo.huaweicloud.com/openeuler/openEuler-22.03-LTS/everything/$basearch/
enabled=1
gpgcheck=1
gpgkey=https://repo.huaweicloud.com/openeuler/openEuler-22.03-LTS/everything/$basearch/RPM-GPG-KEY-openEuler[EPOL]
name=EPOL
baseurl=https://repo.huaweicloud.com/openeuler/openEuler-22.03-LTS/EPOL/multi_version/OpenStack/Train/$basearch/
enabled=1
gpgcheck=1
gpgkey=https://repo.huaweicloud.com/openeuler/openEuler-22.03-LTS/OS/$basearch/RPM-GPG-KEY-openEuler[EPOL5]
name=EPOL5
baseurl=https://repo.huaweicloud.com/openeuler/openEuler-22.03-LTS/EPOL/main/$basearch/
enabled=1
gpgcheck=1
gpgkey=https://repo.huaweicloud.com/openeuler/openEuler-22.03-LTS/OS/$basearch/RPM-GPG-KEY-openEuler[EPOL2]
name=EPOL2
baseurl=https://repo.huaweicloud.com/openeuler/openEuler-22.03-LTS/EPOL/multi_version/OpenStack/Wallaby/$basearch/
enabled=1
gpgcheck=1
gpgkey=https://repo.huaweicloud.com/openeuler/openEuler-22.03-LTS/OS/$basearch/RPM-GPG-KEY-openEuler[EPOL3]
name=EPOL3
baseurl=https://repo.huaweicloud.com/openeuler/openEuler-22.03-LTS/EPOL/update/multi_version/OpenStack/Train/$basearch/
enabled=1
gpgcheck=1
gpgkey=https://repo.huaweicloud.com/openeuler/openEuler-22.03-LTS/OS/$basearch/RPM-GPG-KEY-openEuler[EPOL4]
name=EPOL4
baseurl=https://repo.huaweicloud.com/openeuler/openEuler-22.03-LTS/EPOL/update/multi_version/OpenStack/Wallaby/$basearch/
enabled=1
gpgcheck=1
gpgkey=https://repo.huaweicloud.com/openeuler/openEuler-22.03-LTS/OS/$basearch/RPM-GPG-KEY-openEuler[debuginfo]
name=debuginfo
baseurl=https://repo.huaweicloud.com/openeuler/openEuler-22.03-LTS/debuginfo/$basearch/
enabled=1
gpgcheck=1
gpgkey=https://repo.huaweicloud.com/openeuler/openEuler-22.03-LTS/debuginfo/$basearch/RPM-GPG-KEY-openEuler[source]
name=source
baseurl=https://repo.huaweicloud.com/openeuler/openEuler-22.03-LTS/source/
enabled=1
gpgcheck=1
gpgkey=https://repo.huaweicloud.com/openeuler/openEuler-22.03-LTS/source/RPM-GPG-KEY-openEuler[update]
name=update
baseurl=https://repo.huaweicloud.com/openeuler/openEuler-22.03-LTS/update/$basearch/
enabled=1
gpgcheck=1
gpgkey=https://repo.huaweicloud.com/openeuler/openEuler-22.03-LTS/OS/$basearch/RPM-GPG-KEY-openEuler

经测试,华为云镜像下载速度高于阿里云

1.2 运行docker-compose.yml文件失败

错误信息如下:

ERROR: The Compose file './docker-compose.yml' is invalid because: networks.name Additional properties are not allowed ('name' was unexpected)

解决方法:运行以下命令将docker-compose升级,如1.28.5版本

# 下载
curl -L https://github.com/docker/compose/releases/download/1.28.5/docker-compose-`uname -s`-`uname -m` -o /usr/local/bin/docker-compose
# 安装
chmod +x /usr/local/bin/docker-compose

参考链接无法在docker-compose中提供网络名称

1.3 Docker安装seata出错

错误信息如下:

apm-skywalking not enabled
JMX disabled
Affected JVM parameters: -Dlog.home=/root/logs/seata -server -Dloader.path=/lib -Xmx2048m -Xms2048m -Xss640k -XX:SurvivorRatio=10 -XX:MetaspaceSize=128m -XX:MaxMetaspaceSize=256m -XX:MaxDirectMemorySize=1024m -XX:-OmitStackTraceInFastThrow -XX:-UseAdaptiveSizePolicy -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/root/logs/seata/java_heapdump.hprof -XX:+DisableExplicitGC -Xloggc:/root/logs/seata/seata_gc.log -verbose:gc -XX:+PrintGCDetails  -XX:+PrintGCDateStamps -XX:+PrintGCTimeStamps -XX:+UseGCLogFileRotation -XX:NumberOfGCLogFiles=10 -XX:GCLogFileSize=100M -XX:+UnlockExperimentalVMOptions -XX:+UseG1GC -Dio.netty.leakDetectionLevel=advanced -Dapp.name=seata-server -Dapp.pid=1 -Dapp.home=/ -Dbasedir=/ 
OpenJDK 64-Bit Server VM warning: Cannot open file /root/logs/seata/seata_gc.log due to No such file or directoryException in thread "main" java.lang.IllegalArgumentException: Cannot instantiate interface org.springframework.context.ApplicationListener : io.seata.server.ServerApplicationListenerat org.springframework.boot.SpringApplication.createSpringFactoriesInstances(SpringApplication.java:450)at org.springframework.boot.SpringApplication.getSpringFactoriesInstances(SpringApplication.java:432)at org.springframework.boot.SpringApplication.getSpringFactoriesInstances(SpringApplication.java:425)at org.springframework.boot.SpringApplication.<init>(SpringApplication.java:268)at org.springframework.boot.SpringApplication.<init>(SpringApplication.java:246)at org.springframework.boot.SpringApplication.run(SpringApplication.java:1300)at org.springframework.boot.SpringApplication.run(SpringApplication.java:1289)at org.apache.seata.server.ServerApplication.main(ServerApplication.java:30)
Caused by: java.lang.ClassNotFoundException: io.seata.server.ServerApplicationListenerat java.net.URLClassLoader.findClass(URLClassLoader.java:387)at java.lang.ClassLoader.loadClass(ClassLoader.java:418)at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:352)at java.lang.ClassLoader.loadClass(ClassLoader.java:351)at java.lang.Class.forName0(Native Method)at java.lang.Class.forName(Class.java:348)at org.springframework.util.ClassUtils.forName(ClassUtils.java:291)at org.springframework.boot.SpringApplication.createSpringFactoriesInstances(SpringApplication.java:443)... 7 more

关键一条:

OpenJDK 64-Bit Server VM warning: Cannot open file /root/logs/seata/seata_gc.log due to No such file or directory

尝试新建该目录和文件并授权,无效

尝试修改seata目录下application.yml配置文件中的日志文件输出路径:

# 日志配置
logging:config: classpath:logback-spring.xmlfile:path: /usr/local/src/seata/logs/# path: ${user.home}/logs/seata

seata却能够正常启动

seata修改完配置文件后启动:

docker run -d --name seata  -p 8099:8099  -p 7099:7099  -e SEATA_IP=192.168.150.101  -v /usr/local/src/seata:/seata-server/resources --network tjxt  --restart always --ulimit nofile=1024    seataio/seata-server:1.5.2

1.4 Jenkins启动报错

错误信息:

DEPRECATED: The legacy builder is deprecated and will be removed in a future release.Install the buildx component to build images with BuildKit:https://docs.docker.com/go/buildx/permission denied while trying to connect to the Docker daemon socket at unix:///var/run/docker.sock: Post "http://%2Fvar%2Frun%2Fdocker.sock/v1.24/build?buildargs=%7B%22JAVA_OPTS%22%3A%22-Xms300m+-Xmx300m%22%7D&cachefrom=%5B%5D&cgroupparent=&cpuperiod=0&cpuquota=0&cpusetcpus=&cpusetmems=&cpushares=0&dockerfile=Dockerfile&labels=%7B%7D&memory=0&memswap=0&networkmode=default&rm=1&shmsize=0&t=tj-gateway&target=&ulimits=%5B%5D&version=1": dial unix /var/run/docker.sock: connect: permission denied

很明显 permission denied权少权限,这里因为是本地虚拟机,所有直接:

chmod 777 -R /var/run/docker.sock

1.5 修改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

1.6 OpenEuler 22.03LTS系统安装问题

在VMware16 pro安装一直黑屏,无法进行安装系统

方法:VMware16 pro版本不兼容问题,升级到VMware 17pro

2. Day02 我的课表

2.1 数据库连接配置错误

错误信息如下:

java.sql.SQLNonTransientConnectionException: Could not create connection to database server. Attempted reconnect 3 times. Giving up.
Caused by: com.mysql.cj.exceptions.CJException: Unknown database 'tj_learning'

原因及解决方法:以为是拉去nacos共享配置出错,经排查是新建数据库时名称写错为 tj-learning ,修改为tj_learning

2.2 RabbitMQ消息监听

第一次接触,特此记录下。

LessonChangeListener.java

package com.tianji.learning.mq;import cn.hutool.core.collection.CollUtil;
import com.tianji.api.dto.trade.OrderBasicDTO;
import com.tianji.common.constants.MqConstants;
import com.tianji.learning.service.ILearningLessonService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.stereotype.Component;/*** @author: hong.jian* @date 2024-03-08 17:37*/
@Component
@Slf4j
@RequiredArgsConstructor  // 使用构造器,lombok会在编译器生成相应的方法
public class LessonChangeListener {private final ILearningLessonService lessonService;/**** MQ消息发送*         rabbitMqHelper.send(*                 MqConstants.Exchange.ORDER_EXCHANGE, // Exchange*                 MqConstants.Key.ORDER_PAY_KEY,    // Key*                 OrderBasicDTO.builder()*                         .orderId(orderId)*                         .userId(userId)*                         .courseIds(cIds)*                         .finishTime(order.getFinishTime())*                         .build()*         );** @param dto 接受的参数类型为OrderBasicDTO*/@RabbitListener(bindings = @QueueBinding(value = @Queue(value = "learning.lesson.pay.queue", durable = "true"),exchange = @Exchange(value = MqConstants.Exchange.ORDER_EXCHANGE, type = ExchangeTypes.TOPIC),key = MqConstants.Key.ORDER_PAY_KEY))public void onMsg(OrderBasicDTO dto) {log.info("LessonChangeListener接收消息,用户{},添加课程{}", dto.getUserId(), dto.getCourseIds());// 校验if (dto.getUserId() == null|| dto.getOrderId() == null|| CollUtil.isEmpty(dto.getCourseIds())) {// 这里是接受MQ消息,中断即可,若抛异常,则会开启重试return;}// 保存课程到课表lessonService.addUserLesson(dto.getUserId(),dto.getCourseIds());}}

相关概念详见MQ基础.md

3. Day03 学习计划和进度

3.1 前端接口请求参数异常

请求/ls/lessons/plans参数如图:

在这里插入图片描述

断电调试时接口如下,并未收到参数:

在这里插入图片描述

解决方法:前端使用application/json进行传递参数,需要使用@RequestBody进行接收

/*** 创建学习计划**/
@ApiOperation("创建学习计划")
@PostMapping("/plans")
public void createLessonPlan(@RequestBody LearningPlanFormDTO dto) {learningLessonService.createLessonPlan(dto.getCourseId(),dto.getFreq());
}

4. Day04 改造提交学习记录

4.1 学习记录改造后出现NPE

控制台日志如下:

17:41:51.233-[DESKTOP-CSPG5LG][4dae58c8c33e4b1a9de75ec60e90c60a]--DEBUG 24112 --- [nio-8090-exec-1] c.t.l.m.LearningRecordMapper.update      : ==> Parameters: 137(Integer), true(Boolean), 2024-03-11T17:41:51(LocalDateTime), 1767122184276279297(Long)
17:41:51.237-[DESKTOP-CSPG5LG][4dae58c8c33e4b1a9de75ec60e90c60a]--DEBUG 24112 --- [nio-8090-exec-1] c.t.l.m.LearningRecordMapper.update      : <==    Updates: 1
17:41:51.238-[DESKTOP-CSPG5LG][4dae58c8c33e4b1a9de75ec60e90c60a]--ERROR 24112 --- [nio-8090-exec-1] c.t.c.a.m.advice.CommonExceptionAdvice   : 其他异常 uri : /learning-records -> 
java.lang.NullPointerException: nullat com.tianji.learning.util.LearningRecordDelayTaskHandler.cleanRecordCache(LearningRecordDelayTaskHandler.java:148) ~[classes/:na]at com.tianji.learning.service.impl.LearningRecordServiceImpl.handleVideoRecord(LearningRecordServiceImpl.java:236) ~[classes/:na]at com.tianji.learning.service.impl.LearningRecordServiceImpl.submitLearningRecord(LearningRecordServiceImpl.java:134) ~[classes/:na]

出错部分代码如下:

 Integer allSections = courseInfo.getSectionNum();   // 该课程所有小节数
// 判断该课程所有小节是否已学完
boolean allFinished = learningLesson.getLearnedSections() + 1 >= allSections;
// 更新课表信息:课表状态,已学小节数,最近学习小节id,最近学习时间
learningLessonService.lambdaUpdate()// 如果当前小节未学完,更新最近学习小节id和最近学习时间.set(LearningLesson::getLatestSectionId, learningLesson.getLatestSectionId() + 1).set(LearningLesson::getLatestLearnTime, dto.getCommitTime())// 如果当前小姐已学完,更新已学小节数.set(LearningLesson::getLearnedSections, learningLesson.getLearnedSections())// 如果该课表所以小节已学完,则更新课表状态为已学完.set(allFinished, LearningLesson::getStatus, LessonStatus.FINISHED)// 首次学习需要将状态由未开始更新为学习中// .set(learningLesson.getLearnedSections() == 0 , LearningLesson::getStatus, LessonStatus.LEARNING).set(learningLesson.getStatus() == LessonStatus.NOT_BEGIN, LearningLesson::getStatus, LessonStatus.LEARNING).eq(LearningLesson::getId, learningLesson.getId()).update();

原因:在创建学习计划后,learned_sections字段由于未赋值为null,所以报了NPE

解决方法:在数据库表结构给该字段赋初值0。

5. Day05 问答系统

5.1 用户端分页查询评论时昵称错乱问题

原因:对于目标回复用户收集其target_user_id时和user_id混在一起,导致错误;

解决方案:将二者分开处理

 /*** 管理端分页查询回答或评论列表* @param pageQuery 分页参数* @return  分页列表*/@Overridepublic PageDTO<ReplyVO> pageAdmin(ReplyPageQuery pageQuery) {// 校验问题id和回答id是否都为空if (pageQuery.getAnswerId() == null && pageQuery.getQuestionId() == null) {throw new BadRequestException("查询参数错误");}// 分页查询回答或评论列表Page<InteractionReply> replyPage = this.lambdaQuery()// 如果传问题id就把问题id作查询条件.eq(pageQuery.getQuestionId() != null, InteractionReply::getQuestionId, pageQuery.getQuestionId()).eq(InteractionReply::getAnswerId, pageQuery.getAnswerId() == null ? 0L : pageQuery.getAnswerId())     // 字段默认值0.page(pageQuery.toMpPage(new OrderItem(DATA_FIELD_NAME_LIKED_TIME, false),new OrderItem(DATA_FIELD_NAME_CREATE_TIME, true))); // 按照点赞次数降序排序降序,创建时间升序排序List<InteractionReply> records = replyPage.getRecords();if (CollUtil.isEmpty(records)) { // 查询不到,返回空集return PageDTO.of(replyPage, Collections.emptyList());}// 关联用户信息,先收集用户id,封装到mapList<Long> userIds = new ArrayList<>();List<Long> targetUserIds = new ArrayList<>();   // 目标用户idList<Long> targetReplyIds = new ArrayList<>();   // 目标回复idfor (InteractionReply reply : records) {if (!reply.getAnonymity()) { // 非匿名用户需要查询userIds.add(reply.getUserId());// userIds.add(reply.getTargetUserId());}// "target_user_id"字段默认值为0,查询评论时生效if (reply.getTargetUserId() != null && reply.getTargetUserId() > 0) {targetUserIds.add(reply.getTargetUserId());}// "target_reply_id"字段默认值为0,查询评论时生效if (reply.getTargetReplyId() != null && reply.getTargetReplyId() > 0) {targetReplyIds.add(reply.getTargetReplyId());}}// 查询目标回复列表并封装为MapMap<Long, InteractionReply> targetReplyMap = new HashMap<>();// targetReplyIds不为空,去查询数据库if (!CollUtil.isEmpty(targetReplyIds)) {// 查询目标评论,并封装为MapList<InteractionReply> targetReplies = listByIds(targetReplyIds);targetReplyMap = targetReplies.stream().collect(Collectors.toMap(InteractionReply::getId, reply -> reply));}// 查询用户和目标回复用户并封装为MapMap<Long, UserDTO> userMap = getUserDTOMap(userIds);Map<Long, UserDTO> targetUserMap = getUserDTOMap(targetUserIds);// 保存结果List<ReplyVO> replyVOS = new ArrayList<>();for (InteractionReply reply : records) {ReplyVO replyVO = BeanUtil.toBean(reply, ReplyVO.class);UserDTO userDTO = userMap.getOrDefault(reply.getUserId(), null);// 如果当前回答或评论匿名,不进行赋值if (!replyVO.getAnonymity() && userDTO != null) {replyVO.setUserIcon(userDTO.getIcon()); // 回答人头像replyVO.setUserName(userDTO.getName()); // 回答人昵称replyVO.setUserType(userDTO.getType()); // 回答人类型}UserDTO targetUserDTO = targetUserMap.getOrDefault(reply.getTargetUserId(), null);InteractionReply targetReply = targetReplyMap.getOrDefault(reply.getTargetReplyId(), null);// 如果目标评论匿名,不进行赋值if (targetReply != null && !targetReply.getAnonymity() && targetUserDTO != null) {    // 目标回复非匿名才赋值replyVO.setTargetUserName(targetUserDTO.getName()); // 目标用户昵称}replyVOS.add(replyVO);}// 返回结果return PageDTO.of(replyPage, replyVOS);}

7. Day07 积分系统

7.1 mybatis-plus查询报错

错误信息:

Caused by: org.apache.ibatis.exceptions.PersistenceException: 
### Error querying database.  Cause: java.lang.IndexOutOfBoundsException: Index 2 out of bounds for length 2

相关代码:

    /*** 获取今日积分** @return 今日积分列表*/@Overridepublic List<PointsStatisticsVO> getTodayPoints() {// 获取当前登录用户Long userId = UserContext.getUser();// 分类查询当前用户今日所得各类积分LocalDateTime now = LocalDateTime.now();LocalDateTime dayStartTime = DateUtils.getDayStartTime(now);    // 当前开始时间LocalDateTime dayEndTime = DateUtils.getDayEndTime(now);    // 当天结束时间QueryWrapper<PointsRecord> wrapper = new QueryWrapper<>();// 封装查询wrapperwrapper.select("type", "sum(points) as tmp")  // 查询类型和该类型积分数(使用临时变量暂存暂存BigDemical类型数据).eq("user_id", userId) // 当前用户.between("create_time", dayStartTime, dayEndTime)  // 当天.groupBy("type");   // 根据类型分类wrapper.select("type", "sum(points) as userId");List<PointsRecord> records = this.list(wrapper);// 判空if (CollUtil.isEmpty(records)) {return Collections.emptyList();}// 封装到VO并返回List<PointsStatisticsVO> voList = records.stream().map(record -> {PointsStatisticsVO recordVO = new PointsStatisticsVO();recordVO.setPoints(record.getUserId().intValue()); // 该类型今日积分数,临时变量暂存recordVO.setType(record.getType().getDesc()); // 该类型名称recordVO.setMaxPoints(record.getType().getMaxPoints());   // 该类型上限积分数return recordVO;}).collect(Collectors.toList());return voList;}

原因:使用了PointsRecord存储查询结果,但该类没有构造器

解决方案:在PointsRecord添加@NoArgsConstructor

8. Day08 排行榜

8.1 xxl-job配置

原因:xxl-job在Springboot项目中集成后,需要手动新增相应的执行器和执行任务,不会自动新增,踩坑。

8.2 SpringBoot项目启动后立即停止

原因:SpringBoot启动后控制台无报错,但立即停止,缺少web依赖

解决方法:补充依赖spring-boot-starter-web如下

<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId>
</dependency>

8.3 使用xxl-job迁移分表数据报错

错误信息:提示重复主键

Caused by: org.springframework.dao.DuplicateKeyException: com.tianji.learning.mapper.PointsRecordMapper.insert (batch index #1) failed. Cause: java.sql.BatchUpdateException: Duplicate entry '1773603384562077753' for key 'points_record.PRIMARY' ; Duplicate entry '1773603384562077753' for key 'points_record.PRIMARY'; 

在这里插入图片描述

原因:动态表名插件配置及使用错误

应该是先从当前赛季积分明细表分页查询数据后,更新动态表名,再插入到上赛季积分明细表

// 声明动态表名拦截器,使用拦截器插件
@Bean
public DynamicTableNameInnerInterceptor dynamicTableNameInnerInterceptor() {// 准备一个Map,用于存储TableNameHandlerMap<String, TableNameHandler> map = new HashMap<>(1);// 存入一个TableNameHandler,用来替换points_board表名称// 替换方式,就是从TableInfoContext中读取保存好的动态表名,判空是提高代码健壮性map.put("points_board", (sql, tableName) -> TableInfoContext.getInfo() == null ? tableName : TableInfoContext.getInfo());map.put("points_record", (sql, tableName) -> TableInfoContext.getInfo() == null ? tableName : TableInfoContext.getInfo());return new DynamicTableNameInnerInterceptor(map);
}
// 分页查询上赛季积分明细数据,先构建分页参数
PageQuery pageQuery = new PageQuery();
pageQuery.setPageNo(sharedIndex + 1); // 页码
pageQuery.setPageSize(50);    // 页面记录数while (true) {log.debug("当前页:{}", pageQuery.getPageNo());TableInfoContext.setInfo(null); // 使用默认表名,points_record// 按照id升序查询Page<PointsRecord> recordPage = pointsRecordService.page(pageQuery.toMpPage("id",true));if (CollUtil.isEmpty(recordPage.getRecords())) {  // 结束循环break;}// 翻页,跳过N个页,N就是分片数量pageQuery.setPageNo(pageQuery.getPageNo() + shardTotal);   // 页码+total,跳过N页// 使用动态表名,points_record_5TableInfoContext.setInfo(targetTableName);// 持久化到db相应的赛季表中,批量新增pointsRecordService.saveBatch(recordPage.getRecords());
}

9. Day09 优惠券管理

9.1 新增优惠券mybatis错误

错误信息:

### The error may exist in com/tianji/promotion/mapper/CouponMapper.java (best guess)
### The error may involve com.tianji.promotion.mapper.CouponMapper.insert-Inline
### The error occurred while setting parameters
### SQL: INSERT INTO coupon  ( id, name,  discount_type, specific, discount_value, threshold_amount, max_discount_amount, obtain_way,       total_num,   user_limit,    creater, updater )  VALUES  ( ?, ?,  ?, ?, ?, ?, ?, ?,       ?,   ?,    ?, ? )
### Cause: java.sql.SQLSyntaxErrorException: 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,

原因:specific是mysql关键字

@ApiModelProperty(value = "是否限定作用范围,false:不限定,true:限定。默认false")
private Boolean specific;

Coupon实体类添加注解 @TableField注解

@ApiModelProperty(value = "是否限定作用范围,false:不限定,true:限定。默认false")
@TableField("`specific`")
private Boolean specific;

9.2 定时开始/结束发放优惠券遇到的bug

Bug1:Mybatis-Plus分页查询时碰到total有值但records为空

原因:Mybatis-Plus分页插件设置了maxLimit单页条数

// 分页插件配置
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
paginationInnerInterceptor.setMaxLimit(200L);   // 单页分页条数限制(默认无限制)
interceptor.addInnerInterceptor(paginationInnerInterceptor);

方法:可以适当增大maxLimit或者修改代码:

// 分页插件配置
PaginationInnerInterceptor paginationInnerInterceptor = new PaginationInnerInterceptor(DbType.MYSQL);
paginationInnerInterceptor.setMaxLimit(200L);   // 单页分页条数限制(默认无限制)
paginationInnerInterceptor.setOverflow(true);   // 溢出总页数后是否进行处理(默认不处理)
interceptor.addInnerInterceptor(paginationInnerInterceptor);

注:

Mybatis-Plus分页参数说明如图所示:

在这里插入图片描述

参考链接:Mybatis-Plus分页插件参数说明

Bug2:xxl-job分片广播

原因:xxl-job结合分页时部分日志异常,如页号,推测可能局部配置异常或者内部bug,

	 /*** 定时开始发放优惠券* 利用XXL-JOB的数据分片功能实现*/@XxlJob("couponIssueJobHandler")public void issueCoupon(LocalDateTime now, int shardTotal, int shardIndex) {log.info("开始执行定时开始发放优惠券任务...");PageQuery pageQuery = new PageQuery();pageQuery.setPageNo(shardIndex + 1);    // 页码(分片索引从0开始,页码从1开始)pageQuery.setPageSize(20);   // 页面大小,根据数据量动态调整while (true) {// 分页查询所有未开始的,发放开始时间早于当前时间的优惠券Page<Coupon> couponPage = couponService.lambdaQuery().eq(Coupon::getStatus, CouponStatus.UN_ISSUE)   // 查询未开始状态的.le(Coupon::getIssueBeginTime, now)     // 发放开始时间早于当前时间.page(pageQuery.toMpPage("id", true));// 根据id进行排序,避免重复处理数据List<Coupon> records = couponPage.getRecords();if (CollUtil.isEmpty(records)) { // 判空break;}// 更新优惠券状态records.stream().forEach(coupon -> coupon.setStatus(CouponStatus.ISSUING));// 批量更新优惠券couponService.updateBatchById(records);// 处理下一页数据if (couponPage.hasNext()) {// 翻页,数量为总分片数pageQuery.setPageNo(pageQuery.getPageNo() + shardTotal);} else {break;}}log.info("完成定时开始发放优惠券任务...");}

11. Day11领取优惠券优化

11.1 RabbitMQ交换机配置错误

错误信息如下:

16:47:54.429-[DESKTOP-CSPG5LG][sys]-- WARN 20360 --- [ntContainer#0-1] o.s.a.r.listener.BlockingQueueConsumer   : Failed to declare queue: coupon.receive.queue
16:47:54.430-[DESKTOP-CSPG5LG][sys]-- WARN 20360 --- [ntContainer#0-1] o.s.a.r.listener.BlockingQueueConsumer   : Queue declaration failed; retries left=1
org.springframework.amqp.rabbit.listener.BlockingQueueConsumer$DeclarationException: Failed to declare queue(s):[coupon.receive.queue]at org.springframework.amqp.rabbit.listener.BlockingQueueConsumer.attemptPassiveDeclarations(BlockingQueueConsumer.java:760) ~[spring-rabbit-2.4.6.jar:2.4.6]at org.springframework.amqp.rabbit.listener.BlockingQueueConsumer.passiveDeclarations(BlockingQueueConsumer.java:637) ~[spring-rabbit-2.4.6.jar:2.4.6]at org.springframework.amqp.rabbit.listener.BlockingQueueConsumer.start(BlockingQueueConsumer.java:624) ~[spring-rabbit-2.4.6.jar:2.4.6]at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$AsyncMessageProcessingConsumer.initialize(SimpleMessageListenerContainer.java:1376) ~[spring-rabbit-2.4.6.jar:2.4.6]at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$AsyncMessageProcessingConsumer.run(SimpleMessageListenerContainer.java:1221) ~[spring-rabbit-2.4.6.jar:2.4.6]at java.base/java.lang.Thread.run(Thread.java:834) ~[na:na]
Caused by: java.io.IOException: nullat com.rabbitmq.client.impl.AMQChannel.wrap(AMQChannel.java:129) ~[amqp-client-5.14.2.jar:5.14.2]at com.rabbitmq.client.impl.AMQChannel.wrap(AMQChannel.java:125) ~[amqp-client-5.14.2.jar:5.14.2]at com.rabbitmq.client.impl.AMQChannel.exnWrappingRpc(AMQChannel.java:147) ~[amqp-client-5.14.2.jar:5.14.2]at com.rabbitmq.client.impl.ChannelN.queueDeclarePassive(ChannelN.java:1012) ~[amqp-client-5.14.2.jar:5.14.2]at com.rabbitmq.client.impl.ChannelN.queueDeclarePassive(ChannelN.java:46) ~[amqp-client-5.14.2.jar:5.14.2]at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na]at org.springframework.amqp.rabbit.connection.CachingConnectionFactory$CachedChannelInvocationHandler.invoke(CachingConnectionFactory.java:1162) ~[spring-rabbit-2.4.6.jar:2.4.6]at com.sun.proxy.$Proxy223.queueDeclarePassive(Unknown Source) ~[na:na]at org.springframework.amqp.rabbit.listener.BlockingQueueConsumer.attemptPassiveDeclarations(BlockingQueueConsumer.java:738) ~[spring-rabbit-2.4.6.jar:2.4.6]... 5 common frames omitted
Caused by: com.rabbitmq.client.ShutdownSignalException: channel error; protocol method: #method<channel.close>(reply-code=404, reply-text=NOT_FOUND - no queue 'coupon.receive.queue' in vhost '/tjxt', class-id=50, method-id=10)at com.rabbitmq.utility.ValueOrException.getValue(ValueOrException.java:66) ~[amqp-client-5.14.2.jar:5.14.2]at com.rabbitmq.utility.BlockingValueOrException.uninterruptibleGetValue(BlockingValueOrException.java:36) ~[amqp-client-5.14.2.jar:5.14.2]at com.rabbitmq.client.impl.AMQChannel$BlockingRpcContinuation.getReply(AMQChannel.java:502) ~[amqp-client-5.14.2.jar:5.14.2]at com.rabbitmq.client.impl.AMQChannel.privateRpc(AMQChannel.java:293) ~[amqp-client-5.14.2.jar:5.14.2]at com.rabbitmq.client.impl.AMQChannel.exnWrappingRpc(AMQChannel.java:141) ~[amqp-client-5.14.2.jar:5.14.2]... 14 common frames omitted
Caused by: com.rabbitmq.client.ShutdownSignalException: channel error; protocol method: #method<channel.close>(reply-code=404, reply-text=NOT_FOUND - no queue 'coupon.receive.queue' in vhost '/tjxt', class-id=50, method-id=10)at com.rabbitmq.client.impl.ChannelN.asyncShutdown(ChannelN.java:517) ~[amqp-client-5.14.2.jar:5.14.2]at com.rabbitmq.client.impl.ChannelN.processAsync(ChannelN.java:341) ~[amqp-client-5.14.2.jar:5.14.2]at com.rabbitmq.client.impl.AMQChannel.handleCompleteInboundCommand(AMQChannel.java:182) ~[amqp-client-5.14.2.jar:5.14.2]at com.rabbitmq.client.impl.AMQChannel.handleFrame(AMQChannel.java:114) ~[amqp-client-5.14.2.jar:5.14.2]at com.rabbitmq.client.impl.AMQConnection.readFrame(AMQConnection.java:739) ~[amqp-client-5.14.2.jar:5.14.2]at com.rabbitmq.client.impl.AMQConnection.access$300(AMQConnection.java:47) ~[amqp-client-5.14.2.jar:5.14.2]at com.rabbitmq.client.impl.AMQConnection$MainLoop.run(AMQConnection.java:666) ~[amqp-client-5.14.2.jar:5.14.2]... 1 common frames omitted
16:47:59.442-[DESKTOP-CSPG5LG][sys]-- WARN 20360 --- [ntContainer#0-1] o.s.a.r.listener.BlockingQueueConsumer   : Failed to declare queue: coupon.receive.queue
16:47:59.444-[DESKTOP-CSPG5LG][sys]--ERROR 20360 --- [ntContainer#0-1] o.s.a.r.l.SimpleMessageListenerContainer : Consumer threw missing queues exception, fatal=true
org.springframework.amqp.rabbit.listener.QueuesNotAvailableException: Cannot prepare queue for listener. Either the queue doesn't exist or the broker will not allow us to use it.at org.springframework.amqp.rabbit.listener.BlockingQueueConsumer.handleDeclarationException(BlockingQueueConsumer.java:710) ~[spring-rabbit-2.4.6.jar:2.4.6]at org.springframework.amqp.rabbit.listener.BlockingQueueConsumer.passiveDeclarations(BlockingQueueConsumer.java:644) ~[spring-rabbit-2.4.6.jar:2.4.6]at org.springframework.amqp.rabbit.listener.BlockingQueueConsumer.start(BlockingQueueConsumer.java:624) ~[spring-rabbit-2.4.6.jar:2.4.6]at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$AsyncMessageProcessingConsumer.initialize(SimpleMessageListenerContainer.java:1376) ~[spring-rabbit-2.4.6.jar:2.4.6]at org.springframework.amqp.rabbit.listener.SimpleMessageListenerContainer$AsyncMessageProcessingConsumer.run(SimpleMessageListenerContainer.java:1221) ~[spring-rabbit-2.4.6.jar:2.4.6]at java.base/java.lang.Thread.run(Thread.java:834) ~[na:na]
Caused by: org.springframework.amqp.rabbit.listener.BlockingQueueConsumer$DeclarationException: Failed to declare queue(s):[coupon.receive.queue]

原因:交换机类型错误,代码里写的是topic,mq里的是direct

/*** 更新优惠券已发放数量,新增用户券* @param uc   这里需要用到优惠券id和用户id*/
@RabbitListener(bindings = @QueueBinding(value = @Queue(name = "coupon.receive.queue", durable = "true"),exchange = @Exchange(name = MqConstants.Exchange.PROMOTION_EXCHANGE,type = ExchangeTypes.TOPIC),key = MqConstants.Key.COUPON_RECEIVE
))
public void listenCouponReceiveMessage(UserCouponDTO uc){// 更新优惠券已发放数量,新增用户券userCouponService.checkAndCreateUserCoupon(uc);
}

解决方法:删掉现有MqConstants.Exchange.PROMOTION_EXCHANGE交换机,重新创建并绑定消息队列

coupon.receive.queue

11.2 Redis更新优惠券已发放数量的并发问题

问题描述及相关代码:

兑换码领券 UserCouponRedissonMQServiceImpl.exchangeCoupon接口, 更新Redis中指定优惠券的已发放数量的代码如下,在压力测试时会出现数据脏读问题,如领了3张券,issueNum只加了1

// 更新优惠券库的已发放数量+1
redisTemplate.opsForHash().put(PromotionConstants.COUPON_CACHE_KEY_PREFIX + id,"issueNum", String.valueOf(coupon.getIssueNum() + 1));

原因:并发线程角度时数据脏读问题

解决方法,修改代码:

// 更新优惠券的已发放数量+1
redisTemplate.opsForHash().increment(PromotionConstants.COUPON_CACHE_KEY_PREFIX + couponId,"issueNum", 1);

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/302373.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

1.Hexo安装和环境搭建引导

Hexo是一个依赖于一个名为nodejs的程序 因此安装它的方式在Mac和Windows上实际上是一样的 为了在电脑上安装Hexo 需要做两件事 nodejs&#xff0c;基本上是hexo依赖运行的JavaScript框架 Node.js — Run JavaScript Everywheregit&#xff0c;是一个程序&#xff0c;用来管理电…

BurpSuite保姆级教程

Burp Suite下载,破解,代理web,代理模拟器 (一)为Burp Sutie下载运行执行脚本环境(Java) 1.Java官网下载地址&#xff1a;https://www.oracle.com/java/technologies/ 下载Java SE 17.0.8(LTS) 备注&#xff1a;1.2023版Burp Suite 完美的运行脚本的环境是Java17 2.Java8不支持…

数据仓库实践

什么是数据仓库&#xff1f; 数据仓库是一个用于存储大量数据并支持数据分析与报告的系统。它通常用于集成来自不同来源的数据&#xff0c;提供一个统一的视图&#xff0c;以便进行更深入的分析和决策。 数据仓库的主要优势&#xff1f; 决策支持&#xff1a;为企业决策提供可靠…

2024年,AIGC如何渗透我的生活?

本篇博文列举本人最常用的 6 款app中 AIGC 发挥的功能及作用。 Cursor 作为一名科研工作者&#xff0c;平时最常用的软件就是代码编写工具。Cursor内置的Chat功能&#xff0c;可以辅助完成代码编辑&#xff0c;随时随地实现ChatGPT私有化。 Grammarly 可用于Word和Overleaf等…

vue快速入门(三)差值表达式

注释很详细&#xff0c;直接上代码 上一篇 新增内容 插值表达式基本用法插值表达式常用公式 源码 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-wid…

Jmeter针对多种响应断言的判断

有时候response返回的结果并非一种&#xff0c;有多种&#xff0c;需要对这几种进行判断的时候需要使用Bean Shell。 &#xff08;1&#xff09;首先获取响应数据 String response prev.getResponseDataAsString(); ResponseCode 响应状态码 responseHeaders 响应头信息 res…

c++的学习之路:17、stack、queue与priority_queue

摘要 本文主要是介绍一下stack、queue、priority_queue的使用以及模拟实现&#xff0c;文章末附上代码以及思维导图。 目录 摘要 一、stack的介绍和使用 1、stack的介绍 2、stack的使用 3、stack的模拟实现 二、queue的介绍和使用 1、queue的介绍 2、queue的使用 3、…

每日一题:矩阵置零

给定一个 m x n 的矩阵&#xff0c;如果一个元素为 0 &#xff0c;则将其所在行和列的所有元素都设为 0 。请使用 原地 算法。 示例 1&#xff1a; 输入&#xff1a;matrix [[1,1,1],[1,0,1],[1,1,1]] 输出&#xff1a;[[1,0,1],[0,0,0],[1,0,1]]使用两个标记变量。 class Sol…

2024HW --->反序列化漏洞!

对于反序列化&#xff0c;这个漏洞也是常用的&#xff0c;不过涉及到的方面非常非常广&#xff0c;比其他漏洞也难很多 于是本篇文章就分成PHP和JAVA的反序列化来讲讲 1.反序列化 想要理解反序列化&#xff0c;首先就要理解序列化 序列化&#xff1a;把对象转换为字节序列的过…

模拟memcpy和memmove

memcpy是内存复制函数&#xff0c;原型如下 void *memmove(void *dest, const void *src, size_t count) 从src地址复制count个字节到dest 模拟实现 void *memcpy(void *dest, const void *src, size_t count) {if (dest NULL || src NULL)return NULL;void *ans dest;f…

操作系统知识

根据希赛相关视频课程汇总整理而成&#xff0c;个人笔记&#xff0c;仅供参考。 操作系统概述 *进程管理 进程&#xff1a;程序在一个数据集合上运行的过程&#xff0c;它是系统进行资源分配和调度的一个独立单位。由程序块、进程控制块&#xff08;PCB&#xff09;和数据块三…

【Unity灶台】食品加工系统模型搭建

&#x1f468;‍&#x1f4bb;个人主页&#xff1a;元宇宙-秩沅 &#x1f468;‍&#x1f4bb; hallo 欢迎 点赞&#x1f44d; 收藏⭐ 留言&#x1f4dd; 加关注✅! &#x1f468;‍&#x1f4bb; 本文由 秩沅 原创 &#x1f468;‍&#x1f4bb; 收录于专栏&#xff1a;uni…

第九届蓝桥杯大赛个人赛省赛(软件类)真题C 语言 A 组-航班时间

#include<iostream> using namespace std;int getTime(){int h1, h2, m1, m2, s1, s2, d 0;//d一定初始化为0&#xff0c;以正确处理不跨天的情况 scanf("%d:%d:%d %d:%d:%d (%d)", &h1, &m1, &s1, &h2, &m2, &s2, &d);return d …

贪心算法|135.分发糖果

力扣题目链接 class Solution { public:int candy(vector<int>& ratings) {vector<int> candyVec(ratings.size(), 1);// 从前向后for (int i 1; i < ratings.size(); i) {if (ratings[i] > ratings[i - 1]) candyVec[i] candyVec[i - 1] 1;}// 从后…

Redis中的复制功能(三)

复制 服务器运行ID 除了复制偏移量和复制积压缓冲区之外&#xff0c;实现部分重同步还需要用到服务器运行ID(run ID): 1.每隔Redis服务器&#xff0c;不论主服务器还是从服务&#xff0c;都会有自己的运行ID2.运行ID在服务器启动时自动生成&#xff0c;由40个随机的十六进制…

算法刷题Day28 | 93.复原IP地址、78.子集、90.子集II

目录 0 引言1 复原IP地址1.1 我的解题 2 子集2.1 我的解题 3 子集II3.1 我的解题 &#x1f64b;‍♂️ 作者&#xff1a;海码007&#x1f4dc; 专栏&#xff1a;算法专栏&#x1f4a5; 标题&#xff1a;算法刷题Day28 | 93.复原IP地址、78.子集、90.子集II❣️ 寄语&#xff1a…

SD-WAN为出海电商提供了什么支持

出海电商行业的持续发展与壮大&#xff0c;使得网络连接的稳定性和效率成为其成功的关键因素。SD-WAN&#xff08;软件定义广域网&#xff09;作为一种先进的网络解决方案&#xff0c;为出海电商提供了诸多优势和支持。 首先&#xff0c;SD-WAN通过智能路由技术&#xff0c;能够…

【MySQL学习】MySQL的慢查询日志和错误日志

꒰˃͈꒵˂͈꒱ write in front ꒰˃͈꒵˂͈꒱ ʕ̯•͡˔•̯᷅ʔ大家好&#xff0c;我是xiaoxie.希望你看完之后,有不足之处请多多谅解&#xff0c;让我们一起共同进步૮₍❀ᴗ͈ . ᴗ͈ აxiaoxieʕ̯•͡˔•̯᷅ʔ—CSDN博客 本文由xiaoxieʕ̯•͡˔•̯᷅ʔ 原创 CSDN …

MySQL典型示例

目录 1.使用环境 2.设计表 3.创建表 4.准备数据 5.查询 1.使用环境 数据库&#xff1a;MySQL 8.0.30 客户端&#xff1a;Navicat 15.0.12 2.设计表 假设我们已经建好了一个名为test的数据库。我们添加如下几个表&#xff1a;教师、课程、学生、班级、成绩。实体联系图设…

Java Web这一路走来

大部分Java应用都是Web或网络应用&#xff0c;MVC框架在Java框架中有着举足轻重的地位&#xff0c;一开始的Web应用并不现在这样子的&#xff0c;一步一步走来&#xff0c;每一步都经历了无数的血和泪的教训&#xff0c;以史为镜可以知兴替。 1. 草莽时代 早期的Java服务端技…