时长分布
- 基础篇:P1-P22 (3h59m)
- 实战篇:P24-P95 (19h40m) ✔
- 短信登录:24-34 (2h20m) ✔
- 商户查询:35-47 (3h22m) ✔
- 优惠券秒杀:48-77 (8h27m) ✔
- 达人探店:78-81 (1h9m) ✔
- 好友关注:82-87 (1h56m) ✔
- 附近商户:88-90 (1h3m) ✔
- 用户签到:91-95 (1h12m) ✔
- 高级篇:P96-P144 (9h29m)
- 原理篇:P145-P175 (9h38m)
推荐博客
推荐两篇博客,一篇和本文类型相同,为 debug 记录。
野蛮人6号,目前看到的最好的一篇综合 debug,文章质量高,解决方案清晰可行。我自己做的时候遇到了文中没出现的 bug,绝望中去评论区找作者交流来着,他回复得很快很认真嘿嘿,虽然没解决但哥们很开心,然后凌晨回去查出来了。
另一篇为笔记,非常详尽,涵盖所有内容。
夏雪冬蝉,真神。本文涵盖了本项目所有重要代码,所有知识点,非常牛逼的一份笔记。我觉得秒杀优化之后的部分都挺简单的,网课听懂了代码直接抄都行,文中几乎没有错误。
Day 1 - 配置环境
工具准备: 导入 SQL,下载 Navicat。
配置笔记: 配环境配半天,不像外卖一样好配,改的东西还挺多的。
- 数据库名称: 注意一致,比如 IDEA 里的 db,Navicat 里新建都用
hmdp。 - Nginx 启动: 前端启动 nginx 之前,先在
nginx-1.18.0文件夹里创建两个空文件夹,命名为client_body_temp和temp,不然跑不起来。 - Maven POM: 修改参考了这篇文章:黑马点评之导入初始项目(java)。
- POM 修改完,Maven 得 reload,时间会比较长(我电脑性能网速都好还花了 3min)。
- JDK 版本: 我用的 JDK 17,注意在 Project Structure, Language Level, SpringBoot 的 Configuration 里都要修改。
Day 2 - 短信登录 (P28)
若 UserServiceImpl 类中这句代码报错,类前加 @Slf4j 注释。
log.debug("发送短信验证码成功,验证码:{}", code);测试注意: P28 手机号登录时需要勾选页面下方“已阅读协议”按钮,可能点选不上,点击按钮上方能点上。多点几次吧,烦死我了,测试的大部分时间点不上。
Day 2 - 登录校验 (P29)
环境问题很多,等我做完之后发一下我的 POM 文件。这篇博客挺有用的,主要把 SpringBoot 从 2 换到 3,添加 Jakarta 注解 API。但这需要把代码里面的 javax 导包都改成 Jakarta,改的蛮多,所以我没改哈哈,只把其他的都更新成较新版本了。我目前还用的 SpringBoot 2。
Bug 修复: UserServiceImpl 文件里调试完这句代码有问题。MyBatis 里的问题,导致服务器异常。
User user = query().eq("phone", phone).one();现象:
输入未注册手机号 + 正确验证码 -> 点击登录 -> 界面不跳转,上方弹窗“服务器异常”,数据库中未写入信息。
输入已注册手机号 + 正确验证码 -> 点击登录 -> 返回主界面(符合预期)。
原因: 我用的黑马 GitHub 上源码,非网上各种奇怪版本。区别为,源码版本数据库中数据少,其他版本经他人使用,脏数据多。我新注册的第一个手机号 id 为 1010,第二个为 1011。
解决方案:
User user = baseMapper.selectOne(
new QueryWrapper<User>()
.eq("phone", phone) // 确保字段名与数据库一致
);LoginInterceptor 修改: 强转 user 类型为 UserDTO,若用 User 会报错。
UserHolder.saveUser((UserDTO) user);Day 2 - 环境崩了 - 重新开始
拦截器写完了之后,bug 太多修不过来,起也起不来,放弃 debug。按这篇博客提供的 init 工程,重新开始。
Day 2 - 短信登录 - P28 (二周目)
application.yaml 里需要修改数据库的配置。
Bug:Public Key Retrieval is not allowed 实现短信登录时,输入未注册手机号与正确验证码后,点击登录,界面能正常跳转,但终端报错:
ERROR 25388 --- [nio-8081-exec-1] com.zaxxer.hikari.pool.HikariPool : HikariPool-1 - Exception during pool initialization.
java.sql.SQLNonTransientConnectionException: Public Key Retrieval is not allowed
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:110) ~[mysql-connector-java-8.0.30.jar:8.0.30]- 原因: MySQL 8.0+ 默认使用了
caching_sha2_password,客户端连接未正确配置。 - 解决方案: 数据库连接 URL 中添加
allowPublicKeyRetrieval=true&useSSL=false。
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/hmdp?useSSL=false&allowPublicKeyRetrieval=true&serverTimezone=UTCDay 2 - 登录校验 - P29 (二周目)
开屏弹幕说如果拦截跳转异常,看到 P33 就好了。但愿如此。
- 拦截器实现方法快捷键:
Ctrl + I。 - 视频里说强转成
User类型,我还是先按UserDTO去做了。
UserHolder.saveUser((UserDTO) user);- 果然拦截器报错。有弹幕说先往后看,之后会解决。(P33看完回来):确实先不用管,现在改了,之后会再改。
Session 存储: 保存用户信息到 session 中,用的是 Hutool 的 BeanUtil.copyProperties(),不是 Spring 的。
// Hutool 用法(推荐)
UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
session.setAttribute("user", userDTO);Day 2 - 源码问题?(P30 - P34)
- P30: 登录没有
/me请求,成功登录后跳转主页,点击“我的”能看到默认用户信息(可爱多/女生头像)。 - P33: 注入 Redis 后(快捷键
Alt + Insert插入构造器),点击登录仍跳转主页,与视频演示不一致。 - P34 (RefreshTokenInterceptor):
- 两处判空直接放行,是为了防止 Redis 空指针异常。
- 做完后,登录仍然返回主界面,未显示个人信息(一闪而过,头像小猫)。
/me请求 401。 - 尝试: 去掉
RefreshTokenInterceptor里的两个if,个人信息界面正常显示,但报“服务器异常”。
最终调试结果 (2025.7.10 凌晨 3:30): 我最后调的状态是:点击登录后,/login 报 200,跳转主页。点击“我的”,/me 接口报 200,但前端返回了个 undefined,弹窗“服务器异常”。

- IDEA 报错:
MethodArgumentTypeMismatchException: Failed to convert value of type 'java.lang.String' to required type 'java.lang.Long'- 根本原因:
NumberFormatException: For input string: "undefined"前端传递了一个非数字值 "undefined" 给后端,后端尝试将其转为 Long 时失败。应该是前端问题,不搞了。
Day 2 - 我是傻逼 (P37)
写 P37 缓存作业时查了之前的代码,发现 User 实现类里把 User 对象转为 HashMap 存储时,定义的 tokenKey 传错参了。token 写成 phone 了... 源码没问题。
String tokenKey = LOGIN_USER_KEY + token;P37 直接抄的作业:黑马Redis实战篇.03.练习题—给商铺类型加缓存
Day 2 - 双写一致 (P39)
没用 Postman,用的 Apifox,构造请求麻烦点。全网没找着接口文档。

Day 3 - 互斥锁 (P44)
JMeter 使用: 本节末尾需要 JMeter。
- 下载: Apache JMeter - Download (我用的 5.6.3)
- 配置教程: JMeter安装和使用教程 - 知乎
- 启动: 直接在
bin目录里 cmd,运行jmeter命令。 - 测试结果: 吞吐量 199.8(预期 200 左右)。IDEA 报错
non null key required可忽略。
DoubleCheck (P44, 45)
获取锁成功后应 再次检测 Redis 缓存是否过期,如果存在则无需重建缓存。网课中没有代码,需自己手写。若不添加此代码,请求会打到数据库。
逻辑过期 (P45)
- 参考博客: Redis-黑马点评项目-04-使用逻辑过期来解决缓存击穿问题
- 注意事项: 手动添加测试类,进行单元测试后,后续操作才有效(缓存预热)。
- 测试结果: IDEA 中数据库只执行一条 SQL,证明并发安全。JMeter 出现四种状况(店铺不存在、异常、旧数据、新数据),符合逻辑过期特性。
封装工具类 (P46)
泛型、函数式编程需要理解。最终实际测试结果同网课,成功。
Day 3 - 优惠券秒杀 (P49, 50, 52)
数据准备: 单元测试生成 30000 个 id。使用 Apifox 测试,JSON 数据如下(注意时间设置):
{
"shopId": 1,
"title": "100元代金券",
"subTitle": "周一至周五均可使用",
"rules": "全场通用\\n无需预约\\n可无限叠加\\不兑现、不找零\\n仅限堂食",
"payValue": 8000,
"actualValue": 10000,
"type": 1,
"stock": 100,
"beginTime": "2025-06-26T10:09:17",
"endTime": "2025-06-26T12:09:04"
}
- 配置: 头名称是
authorization,不含冒号和 token,值从 token 里取。 - 参考: 【Bug记录】黑马点评使用jmeter进行秒杀抢购时报401...
- 结果: 预期 200,若不添加 JSON 断言,异常率为 0。
Day 3 - 一人一单 & 集群 (P54, 55)
- P54: POM 添加
aspectjweaver(版本 1.9.4 或 1.9.7)。悲观锁压测结果:99.5% 异常,成功一次(符合预期)。 - P55 (集群):
- 配置 VM options:
-Dserver.port=8083。 - Nginx: 配置文件注意 42/43 行。重载命令
nginx.exe -s reload。修改完记得保存。 - 测试: 两个 JVM 测秒杀时,使用没抢过秒杀券的用户。
Day 4 - 分布式锁 (P58 - P65)
锁接口 ILock.java:
public interface ILock {
/**
* 尝试获取锁
* @param timeoutSec 锁持有的超时时间,过期自动释放
* @return true表示获取锁成功,false表示获取锁失败
*/
boolean tryLock(long timeoutSec);
/**
* 释放锁
*/
void unlock();
}Redisson (P65):
- Apifox 报错:
connect: connection refused-> 原因是 Apifox 插件没开权限。 - Redisson 配置:
config.useSingleServer().setAddress("redis://localhost:6379");- 测试: 俩 JVM 测,异常 99.5%,只下了一单。
Day 5 - 秒杀优化 (P70 - P76)
- P70 (Redisson 版本): 我用的 3.36.0,Warn 报 Netty 版本过低。尝试显式声明 Netty 4.1.108 未成功,仍然使用 SpringBoot 2.7.4 默认版本,但不影响使用。
- IDEA 技巧:
Ctrl + Shift + Alt + J修改同名变量。 - P71 (阻塞队列): 没法完全测,因为没有网课给的 1000 用户 token。
- P76 (Lua 脚本):
- 下载 Lua 依赖 (EmmyLua)。
- unlock.lua:
-- 比较线程标示与锁中的标示是否一致
if(redis.call('get', KEYS[1]) == ARGV[1]) then
-- 释放锁 del key
return redis.call('del', KEYS[1])
end
return 0- Java 调用:
stringRedisTemplate.execute(
UNLOCK_SCRIPT,
Collections.singletonList(KEY_PREFIX + name),
ID_PREFIX + Thread.currentThread().getId());Day 6 - 配置新 Redis (P77) & 附近商户 (P90)
Redis Stream 问题: 使用 XGROUP 命令需要 Redis > 5.0,默认使用的 Redis 为 3.2 版本。
- 解决方案: Linux 配新 Redis,或寻找 Win 版 Redis 5.0+ MSI 安装包。
- 建议: 有大佬建议 Stream 部分可以跳过,本项目中 Stream 比较简陋,实际开发多用 MQ。
附近商户 (P90): 需要改 POM 文件,使用 Redis 6.2 提供的 geosearch 命令。
Day 0 - 评论区大佬的建议
实战篇学完了,感觉质量最高的还是在 P71 之前那部分。
- 后面的话像 Redis 做消息队列一般公司也不会用,随便看看就行。
- 达人探店开始的话就比较基础了,价值一般。
- 好友关注的 Feed 流如果不懂的话,这里概念讲的可以,堪称百万 PPT,但是代码实现上很明显没有之前的质量高了,是达不到企业标准的。
- 再往后的其实就是对三个高级数据结构的应用,这里还是不错的,如果没有企业实习的经历,这个能开拓眼界。
过来人提醒下: 不要用 Stream,可以跳过不看,用专业的消息队列中间件,同时准备好消息队列的八股,否则简陋的 Stream 很容易被问死。异步持久化还存在消息丢失、消息重复消费的幂等性问题尤其要注意。另外生成分布式唯一 id 的方案也不太行,高度依赖 Redis 的可用性,最好用雪花算法。
