当前位置: 首页 > news >正文

一次 GitHub Actions 翻车实录:E2E 测试把我的后端项目按在地上做体检

最近我给 FlashRisk 项目加了一个看起来很高级的东西:Testcontainers 端到端测试

理想很丰满:

一条命令启动 MySQL、Redis、Kafka,再拉起所有微服务,最后模拟注册、登录、库存预热、下单、风控、结算。

现实很直接:

GitHub Actions:你先别急着高级,我先让你看看什么叫红色感叹号。


一、事故现场

GitHub 上 CI 挂了:

FlashRisk CI / Maven Verify (push) Failing

失败点在:

Run unit and integration tests mvn -B verify -DskipITs=false

本地看起来没啥问题,但 GitHub Actions 一跑完整 E2E,直接翻车。

这就是 E2E 测试的魅力:
单元测试像体检抽血,E2E 测试像让你跑 1000 米。问题藏不住。


二、第一个错误:MySQL 初始化权限不够

日志核心报错:

Access denied for user 'flashrisk'@'%' to database 'campaign_db'

当时的 E2E MySQL 配置大概是这样:

privatestaticfinalMySQLContainer<?>MYSQL=newMySQLContainer<>(DockerImageName.parse("mysql:8.0")).withDatabaseName("user_db").withUsername("flashrisk").withPassword("flashrisk_dev_password").withInitScript("e2e/mysql-init.sql");

问题就在withInitScript()

它会通过 JDBC 执行初始化 SQL,而这个 JDBC 连接使用的是我们配置的业务用户:

flashrisk

但初始化 SQL 里要做这些事情:

CREATEDATABASEIFNOTEXISTScampaign_db;CREATEDATABASEIFNOTEXISTSorder_db;CREATEDATABASEIFNOTEXISTSrisk_db;GRANTALLPRIVILEGES...

这就很尴尬了。

业务用户flashrisk的权限定位是:

好好读写业务库,别想着当 DBA。

结果我们让它去创建数据库、授权用户。
这就像让实习生第一天上班就去改公司组织架构,系统当然说:你不配。


三、第一个修复:让 MySQL root 初始化脚本

正确做法是:
不要用withInitScript()执行多库初始化脚本,而是把 SQL 文件挂到 MySQL 官方镜像的初始化目录:

/docker-entrypoint-initdb.d/

修复后代码:

privatestaticfinalMySQLContainer<?>MYSQL=newMySQLContainer<>(DockerImageName.parse("mysql:8.0")).withDatabaseName("user_db").withUsername(DB_USER).withPassword(DB_PASSWORD).withCopyFileToContainer(MountableFile.forClasspathResource("e2e/mysql-init.sql"),"/docker-entrypoint-initdb.d/001-init-flashrisk.sql");

这样 SQL 会在 MySQL 容器初始化阶段执行,由镜像内部用 root 权限处理。

一句话总结:

withInitScript()适合普通建表;
/docker-entrypoint-initdb.d/更适合创建数据库、创建用户、授权这种“管理员活儿”。


四、第二个错误:Gateway JWT issuer 翻车

第一个问题修完后,CI 往下跑,终于进入业务链路。

然后又挂了。

请求:

POST /api/campaigns/1/inventory/preheat

返回:

500 Internal Server Error

注意:注册、登录都成功了。
说明 JWT 生成没问题,问题出在带 Token 访问受保护接口时。

Gateway 里原来的代码是:

headers.set(AUTH_ISSUER_HEADER,jwt.getIssuer().toString());

看起来优雅,实际上暗藏小坑。

我们的 issuer 是:

flashrisk-user-service

它是一个普通字符串,不是 URL。

而 Spring Security 的jwt.getIssuer()更偏向把iss当成 URL/URI 语义处理。
普通字符串可能拿不到有效 issuer,结果这里一.toString(),直接空指针。

这个错误很有节目效果:

JWT 明明带了iss,但是你非要它长得像 URL。
它只是个服务名,不是网址。你不能要求每个人上班都穿西装。


五、第二个修复:直接读取原始issclaim

修复前:

headers.set(AUTH_ISSUER_HEADER,jwt.getIssuer().toString());

修复后:

Stringissuer=jwt.getClaimAsString("iss");if(issuer!=null&&!issuer.isBlank()){headers.set(AUTH_ISSUER_HEADER,issuer);}

这样就不会强行把 issuer 当 URL 解析了。

同时补了一个单元测试,专门验证这种普通字符串 issuer:

Map.of("iss","flashrisk-user-service","sub","1001","username","alice")

测试目标很明确:

issuer 不像 URL,也不准 Gateway 原地爆炸。


六、最终结果

修复后重新推送:

fix: initialize e2e mysql schemas as root fix: relay jwt issuer claim safely

本地验证:

mvn-q-plgateway-service-amtestmvn-qtest

GitHub Actions 最新结果:

FlashRisk CI / Maven Verify passed

这次 CI 从红变绿,说明完整链路已经能在 GitHub runner 上跑通。


七、这次事故的经验

第一,Testcontainers 的withInitScript()不一定适合做数据库级初始化。
如果涉及CREATE DATABASECREATE USERGRANT,优先考虑 MySQL 官方初始化目录。

第二,JWT 的iss不一定非得是 URL。
如果业务里 issuer 是服务名,例如:

flashrisk-user-service

那就直接用:

jwt.getClaimAsString("iss")

别强行用getIssuer()

第三,E2E 测试很烦,但很有用。
它不会夸你架构优雅,它只会问:

你这一整套东西,真能跑起来吗?

这次它问了。
然后我们修了。
最后 CI 绿了。
技术人的快乐,有时候就是这么朴素。

http://www.jsqmd.com/news/1094105/

相关文章:

  • pg空值管理
  • 深入解析无列名SQL注入:原理、实战与防御
  • Pi Agent 对接实现:消息解析、重试与取消
  • QuantConnect Lean算法交易引擎:从零开始构建专业量化交易系统的完整指南
  • 私教服务 | 他不加班,项目延期了,我该怎么办?
  • 一套 Spec-First 的 AI 编程工作流
  • 基于HAL库的STM32笔记——GPIO
  • 作为Python开发者值得关注的五个第三方库
  • ITSM系统里的工单分类:为什么分类越细,IT服务台反而越难用?
  • AI教材写作新突破!借助AI工具快速编写教材,低查重率不是梦
  • 进阶调节作用分析 | 多个自变量、二分类因变量、有序因变量及面板数据都能做
  • 自动售货机和传统便利店的区别,哪个更有优势?~YH
  • 聚龙汇刘睿 以信任为基石 打造投资社群新生态
  • PHP代码审计实战:preg_match正则绕过与无字母数字WebShell构造
  • 告别付费:Android原生TTS引擎的离线语音合成实战
  • 联想拯救者BIOS隐藏功能解锁:5分钟释放你的笔记本全部性能
  • 2026实测12款论文降AI率软件,效果最好的竟然是它!
  • Agent Runtime 范式革命:从混沌执行到确定性系统
  • 深入解析JavaScript原型链污染:原理、危害与防御实战
  • 2026年为什么越来越多家庭开始重视家庭系统建设?
  • agent 学习
  • AI期刊论文写作工具哪个好?2026年主流工具横向测评
  • 艺起玩一夏 | 暑期用小艺做攻略、听讲解、修美图,轻松玩转沉浸式研学
  • Java毕业设计-基于 SpringBoot 的戏曲文化科普与分享平台设计 传统文化视域下戏曲传播管理系统的设计与实现(源码+LW+部署文档+全bao+远程调试+代码讲解等)
  • 主流办公APP对比,图文会议总结功能谁更实用
  • 2026一线大厂Java面试八股文整理(附答案)| 建议收藏
  • ByteArrayInputStream和DataInputStream的源码分析和使用方法详细分析前言)UTF-8 编码规则
  • 621万vs697万!2026年结婚人数预测你信哪个?
  • 世界模型的PopLang底座:当物理AI遇上ibbot智体机灵,每台手机都能成为“认知推演沙盘”
  • Python列表去重的20种实现方式