Dian Ping debug 2025

Published on
/16 mins read/

时长分布

  • 基础篇: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 记录。

黑马点评问题综合 - CSDN博客

野蛮人6号,目前看到的最好的一篇综合 debug,文章质量高,解决方案清晰可行。我自己做的时候遇到了文中没出现的 bug,绝望中去评论区找作者交流来着,他回复得很快很认真嘿嘿,虽然没解决但哥们很开心,然后凌晨回去查出来了。

另一篇为笔记,非常详尽,涵盖所有内容。

Redis实战(黑马点评--异步秒杀消息队列) - 博客园

夏雪冬蝉,真神。本文涵盖了本项目所有重要代码,所有知识点,非常牛逼的一份笔记。我觉得秒杀优化之后的部分都挺简单的,网课听懂了代码直接抄都行,文中几乎没有错误。


Day 1 - 配置环境

工具准备: 导入 SQL,下载 Navicat。

配置笔记: 配环境配半天,不像外卖一样好配,改的东西还挺多的。

  1. 数据库名称: 注意一致,比如 IDEA 里的 db,Navicat 里新建都用 hmdp
  2. Nginx 启动: 前端启动 nginx 之前,先在 nginx-1.18.0 文件夹里创建两个空文件夹,命名为 client_body_temptemp,不然跑不起来。
  3. Maven POM: 修改参考了这篇文章:黑马点评之导入初始项目(java)
    • POM 修改完,Maven 得 reload,时间会比较长(我电脑性能网速都好还花了 3min)。
  4. 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=UTC

Day 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,弹窗“服务器异常”。

debug
  • 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,构造请求麻烦点。全网没找着接口文档。

debug

Day 3 - 互斥锁 (P44)

JMeter 使用: 本节末尾需要 JMeter。

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"
}
JMeter 压测 (P52):
debug

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 之前那部分。

  1. 后面的话像 Redis 做消息队列一般公司也不会用,随便看看就行。
  2. 达人探店开始的话就比较基础了,价值一般。
  3. 好友关注的 Feed 流如果不懂的话,这里概念讲的可以,堪称百万 PPT,但是代码实现上很明显没有之前的质量高了,是达不到企业标准的。
  4. 再往后的其实就是对三个高级数据结构的应用,这里还是不错的,如果没有企业实习的经历,这个能开拓眼界。

过来人提醒下: 不要用 Stream,可以跳过不看,用专业的消息队列中间件,同时准备好消息队列的八股,否则简陋的 Stream 很容易被问死。异步持久化还存在消息丢失、消息重复消费的幂等性问题尤其要注意。另外生成分布式唯一 id 的方案也不太行,高度依赖 Redis 的可用性,最好用雪花算法。