news 2026/4/26 16:48:22

Java 篇-项目实战-天机学堂(从0到1)-day7

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Java 篇-项目实战-天机学堂(从0到1)-day7

java 篇: 1.基础地基 2.设计原理 3.项目实战


分析产品原型:

表有了,用 mp 生成器,生成相关的实体。

实现签到功能接口:

docker exec -it redis redis-cli

这个命令的意思是:在运行中的 Redis 容器里执行 `redis-cli` 命令

部分含义说明
`docker`Docker 命令调用 Docker 程序
`exec`执行在容器中执行命令
`-it`交互式 + 终端`i`=交互模式,`t`=分配伪终端(两个通常一起用)
`redis`容器名要进入的容器名称
`redis-cli`要执行的命令Redis 的命令行客户端工具

返回的是旧值, 这里从 0 开始

bitfield bm get u2 0

部分含义说明
`BITFIELD`位操作命令Redis 的位域操作命令
`bm`键名要操作的 key
`get`读取操作从位域中读取值
`u2`类型+位数`u`=无符号整数,`2`=占2个bit位
`0`偏移量从第0位开始读取
符号含义取值范围
`u`无符号整数0 到 2^n - 1
`i`有符号整数-2^(n-1) 到 2^(n-1)-1

返回十进制的 3

前端不需要总的,所以用 @JsonIgnore 忽略了

注意划横线这几处

对于日期格式设置了静态 String 类型。

spring 默认返回 true or false,redis 当中是 0 or 1,返回 1,代表旧值已经有了,可以用此判断重复签到了

复习一下:

维度BadRequestExceptionBizIllegalException
含义错误的请求业务非法操作
HTTP状态码400(客户端错误)通常 400 或 403
触发场景参数格式错误、必填项缺失业务规则违反、权限不足
使用者客户端传错了数据客户端做了不该做的操作
能否修复修改请求参数即可可能需要权限或改变状态

①:

部分含义
`redisTemplate.opsForValue()`获取 Redis 字符串操作对象
`.bitField(key, ...)`执行 BITFIELD 位域操作
`BitFieldSubCommands.create()`创建位域命令构建器
`.get(...)`执行读取操作
`BitFieldType.signed(len)`有符号整数,占 len 个 bit 位
`.valueAt(0)`从第 0 位开始读取
`List`返回结果列表

为啥返回的是集合呢,因为这当中可以有很多个子命令,但是这里只执行了一个子命令,得到 Long 的十进制数。

②:为了避免下面 while 当中频繁的拆箱,提前进行了强转处理。

③:复习一下

`>>>` 和 `>>` 的区别:

操作符名称行为
`>>>`无符号右移左边补 0
`>>`有符号右移左边补符号位

这里奖励积分还没有实现,先设置个 0,测试一下

这里采用 0 和 1,在前端看来就是 false or true,并且可以减少请求交互过程中数据的传输,本质是用更少的数据量表达相同的信息,用 1 个 bit 代替 5 个字节

`Byte[]` 是 Java 中的字节数组对象类型。Byte 为 bite 基本数据类型的包装类。

跟前面统计那部分类似,只不过没有传入参数,需要自己拿一下。后面就是把读取的结果转为 Byte[] arr。

实现保存积分明细功能接口:

定义了学习有关的交换机,以及积分相关的路由键

这里就实现写回答和签到记录积分为例子

消息队列名字换了下,然后交换机不变,都是学习相关的,但是路由键肯定得换了。但是下面的是针对签到的,而签到,它加的积分不是固定的,连续签到多天加的积分不同。那这就提前定义了一个枚举类,就不用 map 放 userid 和 points 了,

最后还得把积分的类型传进去。接着跟着流程完成具体的代码编写:

先来判断下,是否有积分上限,其实传入的 type 枚举类当中就有这么一个属性。

因为"每日签到" 一天就一次,"课程评价"对课程评价,也就一次,又不能重复,所以为 0,没有设置上限。那这里就判断 maxPoints 是否 >0 就行了。

如果有上限,就需要进一步判断,是否超过上限。如果没有上限,直接保存积分记录。

对于有上限,我们需要用 userId,type,时间去数据库查现在已得的积分

int currentPoints = queryUserPointsByTypeAndDate(userId, type, begin, end);

那由于 lambda()当中没有.SUM()方法,所以得手写下。

混合使用 LambdaQueryWrapper 和手写 SQL 的典型用法

`Constants.WRAPPER` 的作用

`Constants.WRAPPER` 的值是 `"ew"`(MyBatis-Plus 内部定义)

`@Param(Constants.WRAPPER)` 相当于 `@Param("ew")`

所以在 SQL 中可以用 `${ew.customSqlSegment}` 获取 wrapper 生成的 SQL 片段

`${ew.customSqlSegment}` 会生成什么?

假设参数为:

userId = 1001

type = "SIGN" (签到)

begin = "2026-01-01", end = "2026-01-31"

那么 `${ew.customSqlSegment}` 会被替换为:

`customSqlSegment` 是 MyBatis-Plus 中 `QueryWrapper` 的一个内部属性,它存储了 wrapper 构建的完整 SQL 片段(包括 `WHERE` 关键字和所有条件)。

就是传入的 wrapper 参数,通过 @Param(Constants.WRAPPER)传到了上面的 ew,.customSqlSegment 获得完整 sql 片段,与前面部分拼接构成了完整的 sql 语句。

最后别忘了健壮性处理

因为 userId 肯定不为空的,那 type 和 begin、end,正常情况肯定不为 null,这里也判断一下把。下面如果 wrapper 为 null,那得到的 points 为 null,所以需要判断一下。

那这样就查到了已得积分,如果超过了上限,那就没有必要保存记录了。没有的话,那也不能直接保存,如果说已得积分,加上现在的积分,超过了上限,那就有问题了。所以真实记录的积分应该是上限-当前积分。

ok,完事。

之后在签到里发送消息,测试一下

先注入 RabbitMq 客户端

rewardPoints 是连续签到得到的积分,这里 +1 是本次签到得到的积分,就是传的是总积分。

完整的流程是这样的:

这里发送消息到交换机,并设置了路由键,监听器绑定了队列,监听到队列有消息,开始处理消息,保存数据到数据库。

下面开始测试:

先删除 Redis 当中原先的签到记录,发送请求

redis 当中又有了这个数据

再看 RabbitMq 当中

也是有消息的

并且数据库当中也有记录的数据

从时间上判断也是正确的。

实现查询我的今日积分接口:

这里查询跟之前根据用户类型和数据查询类似,不过这里是要返回每个类型的,不是单个了。

记得起个别名,这样才能和 PointsRecord 当中的属性对上,然后这里是分组查询

查到之后封装返回

进行测试:

测试通过,注意查的是今日的,你得看下数据库当中是否有数据

前端应该是这样的,不过我没看

发现还是不行,突然看到

这里怎么是 java 21(图中是已改过的)

而其他导入存在的是这个 java 11 这样的形式

喔凸(艹皿艹 ),原来是这里没配置 JDK 版本,改好后,果然不报错了。

但是,重测,还是出现了 Learning 下游业务并没有执行,比对了代码,看了 rabbitMq 相关的信息,发现没啥问题,然后相当红温,于是求助企鹅龙虾。先是判断,前端没有传递 `bizType` 参数,于是在

结果发现前端有传

再去看日志发现

`String.format("{}.times.changed", "QA")` 应该返回 `"QA.times.changed"`,而不是 `{}.times.changed`。

这说明传入 send 的 `bizType` 实际上是 `null`。

所以就是在 addLikeRecord 方法和模板化过程中间出了问题

然后列出了这几种可能性

  1. 日志打印和 send 调用之间 bizType 被清空了(不太可能,String 是不可变的)2.
  2. RabbitMQHelper.send 的日志方法打印的参数顺序有问题
  3. Learning 服务连接 RabbitMQ 时出现了问题

于是通过临时硬编码的方式

重新测试,发现好了,下游服务的数据库发生了变化。

所以根本原因是

`String.format` 格式化失败。

一查我的妈呀,

  • 用 `String.format()` 时 → 用 `%s`
  • 用 `StringUtils.format()` 时 → 用 `{}`

怪不得,我一直用的是 `String.format()` 搭配 `{}`,路由键一直是错的,能处理就怪了

最终也是通过了。不过这里还有个小 bug

理论上会有最新回答的

但我前端测试是没有的

不过我也不想改了,后面需要改再说吧。过

功能改进:

原来的流程,涉及到多处数据库的读写,因为这是网课,对于点赞功能没有特别大的需求,再加上设置了用户 id 和业务 id 的联合索引,大部分能满足需求。

改进后

这里对于新增点赞记录,采用 Set,天然保证数据唯一性,新增成功输出 1,失败输出 0.统计采用 ZSet,多实例部署水平扩展,进行刷盘,那为了避免重复刷盘,就需要查到后删除,这样其他实例才能去处理下一部分。不用 Map 是因为它没法保证,查和删原子性操作,且里面的元素是无序的,需要通过 SCAN 扫描全部,数据量是不确定的,这可能会给数据库造成压力。用 ZSet 因为它可以根据 score 进行排序,ZPOP 查删原子性,且可以设置查询数量,避免一次性取过多的数据,给下游造成太大压力。

然后还有一点要提的就是 bigKey 的问题,按业务拆成多个 Key,那当然如果某个业务当中还是出现 bigKey 的问题,继续拆,可以将业务 id 哈希运算然后对 10 取余,余数不同,Key:n 再次打散

改造点赞和取消点赞接口:

这里用接口,因为默认为 public final

前缀命名规律

前缀拆解含义
`likes:set:biz:`likes + set + biz + :点赞业务的 Set 结构,存储业务ID
`likes:times:type:`likes + times + type + :点赞业务的次数字段,按类型分类

层级结构:

copy 原先的,然后把原先的 @Service 注释掉

第一个用 redisTemplate,参数都是 String 类型,得到的结果是 Long 类型,第二个涉及到自动拆箱,得先判断不为 null

下面为主要流程,

框框里也是对于拆箱的处理,因为 score 为 double 类型

改造查询点赞状态接口:

可以直接通过这个命令,查看 id 在不在当中,但是只能看一个业务。想看是不是在其他业务,就得在去 for 执行。

如果客户端与 Redis 服务端之间的距离非常长,导致网络延时很高,那总的网络消耗就会很大。

这里采用批处理的方式

批量发送 n 条命令,执行 n 条命令,返回 n 个结果。

代码实现:

原先的 for 写法(注释中),挨个判断有木有,如果有,把 bizId 加到 Set集合中。

现在以管道模式执行 Redis 命令,:所有命令的执行结果列表(顺序与添加命令的顺序一致)

`RedisCallback` 回调

  • 作用:在 Redis 连接中执行自定义操作
  • 特点:可以获取底层的 `RedisConnection` 对象

new RedisCallback() 本质上就是个匿名类

匿名类的特征

特征说明
没有类名定义时不需要写类名
一次性使用通常只用一次
立即实例化定义的同时就创建对象
编译器生成名字编译后会有 `ClassName$1.class` 这样的文件
不能重复使用无法创建第二个实例

里面的 doInRedis 方法默认返回 null,但没啥干系,需要的结果都在 objects 当中。

当然还可以优化一下

匿名类换成 lambda 表达式,鼠标放在 RedisCallback上有黄色小灯泡,可以立即变的

框里面就是 stream 仙人写法,可读性差,看看就行

定时任务持久化缓存数据:

现在启动类上加上

然后在 remark 模块,新建 task,因为这个模块来统一管理点赞相关的数据,落库都在这完成。

@Scheduled(fixedDelay = 20000),里面也可以用 cron 表达式,定时任务执行时间。前面的 BIZ_TYPES 和 MAX_BIZ_SIZE 可以写到配置文件里,nacos 来管理。这里 MAX_BIZ_SIZE 限制一次性处理最多的业务 id.调用 recordService 进行处理

在原来的 ServiceImpl 和新建的 ServiceRedisImpl 都去实现这个.readLikedTimesAndSendMessage 方法,不过 ServiceImpl 给个壳子就行,避免报错,在 ServiceRedisImpl 具体实现。

那肯定得执行 redisTemplate 当中查找并删除方法,那选 popMin 还是 popMax 呢,这里选小的上,因为小的对数据更加敏感,并且数据量小,估摸着都不会变 ,尽早刷盘把它在 Redis 当中干掉。那大的呢,体量大,差一俩个无所谓,并且数据变化频繁,那尽量减少刷盘的频率。

那这里需要 key 和 maxBizSize 参数,然后加入 key. 得到的 tuples 为元组,就是键值对咯。那它跟 LikedTimesDTO 对应。那后面就是一个个将它们放到 list 集合中,统一发送 MQ

然后修改 learning 模块的 Listener

这里不是处理一个了,而是整个 list 集合了。调整一下,ok

进行测试

测试通过

20s 后这个数据就入库,Redis 当中就没了

压测

新建线程组,右键添加相关组件。当前页面配置说明

当请求失败时,选择如何处理:

选项含义使用场景
继续忽略错误,继续执行压力测试,统计真实错误率 ✅
启动下一进程循环跳过当前,开始下一次循环业务依赖时
停止线程当前线程停止,其他继续某个用户失败后不再重试
停止测试所有线程立即停止严重错误时
立即停止测试强制停止,不等待紧急情况

这里看下定义了解下就行

配置项你的设置含义推荐值说明
线程数2000虚拟用户数量100 → 500 → 2000 逐步增加从低到高测试服务器承受能力
Ramp-Up时间空白 ❌启动所有线程的时间(秒)60-120秒空白=瞬间启动2000线程,容易压崩服务器
循环次数永远1 ⚠️每个线程执行几次1 或 永远选择"1"或"永远",不要两个都选
Same user on each iteration未勾选是否保持同一用户状态看需求勾选=保持Cookie/会话;不勾选=每次模拟新用户
延迟创建线程直到需要未勾选何时创建线程默认不勾选勾选=节省内存;不勾选=提前创建所有线程
调度器未勾选启用定时控制压力测试时勾选不勾选=手动停止;勾选=自动停止
持续时间(秒)未设置整个测试运行时长300秒需要先勾选"调度器"才显示
启动延迟(秒)未设置延迟多久后开始0-10秒需要先勾选"调度器"才显示

字段名你的数值含义判断标准
Label点赞取样器/请求的名称(你在HTTP请求中填写的名称)用于区分不同的请求接口
# 样本2000发送的请求总数(样本数量)数值越大,结果越可信
平均值2ms所有请求的平均响应时间越小越好✅ <200ms 优秀⚠️ 200-500ms 一般❌ >500ms 较差
最小值1ms最快的请求响应时间反映最佳情况
最大值7ms最慢的请求响应时间反映最差情况波动越小越稳定
标准偏差0.67响应时间的波动程度越小越稳定✅ <50 波动小⚠️ 50-100 波动中等❌ >100 波动大
异常 %0.00%请求失败的比例越小越好✅ <1% 优秀⚠️ 1-5% 可接受❌ >5% 有问题
吞吐量966.7/sec每秒处理的请求数(TPS/QPS)越大越好反映服务器的处理能力

如果对你有帮助的话,请点赞,关注,收藏。热爱可抵一切!👍 ❤️ 🔥

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/4/26 16:44:28

Skillz:基于MCP协议实现AI技能跨平台复用的开源服务器

1. 项目概述&#xff1a;Skillz&#xff0c;一个为AI智能体注入“技能”的MCP服务器 在AI智能体&#xff08;Agent&#xff09;的开发和使用中&#xff0c;我们常常面临一个困境&#xff1a;每个智能体平台&#xff08;如Claude Code、Cursor、GitHub Copilot等&#xff09;都…

作者头像 李华
网站建设 2026/4/26 16:42:22

Star-Office-UI:面向现代办公场景的开源Vue 3组件库深度解析

1. 项目概述&#xff1a;一个面向现代办公场景的开源UI组件库最近在做一个内部办公系统的重构&#xff0c;前端界面这块一直是个痛点。市面上的组件库要么太重&#xff0c;要么风格太“通用”&#xff0c;很难满足办公场景下对效率、清晰度和协作感的特定要求。直到我发现了rin…

作者头像 李华
网站建设 2026/4/26 16:41:36

从HmacSHA256到AES:一文搞懂Java KeyGenerator支持的8种算法怎么选

Java加密算法选型指南&#xff1a;从HmacSHA256到AES的8种密钥生成策略 在当今数据驱动的商业环境中&#xff0c;信息安全已成为系统设计的核心考量。作为Java开发者&#xff0c;我们经常需要在API签名、数据库加密或敏感数据传输等场景中选择合适的加密算法。KeyGenerator类支…

作者头像 李华
网站建设 2026/4/26 16:35:40

中国乡村振兴综合调查数据(CRRS)

01、数据介绍中国乡村振兴综合调查&#xff08;China Rural Revitalization Survey&#xff0c;简称CRRS&#xff09;是由中国社会科学院农村发展研究所发起并完成的一项全国大型农村追踪调查。该调查旨在深入贯彻落实国家关于大兴调查研究之风的重要指示&#xff0c;全面、客观…

作者头像 李华
网站建设 2026/4/26 16:35:33

数字经济政策文本-北大法宝2003-2024年

01、数据介绍参考《地理研究》中李研的文献&#xff0c;对北大法宝数字经济政策文本数量代表数字经济政策。首先结合数字经济定义界定有关数字经济发展的关键词&#xff0c;包括数字经济、数字化、大数据、云计算、人工智能、5G、区块链、物联网、智慧交通、智慧能源、智慧医疗…

作者头像 李华