本地接口级 E2E 测试配方:JDK 8、假 PATH 和请求体的三个坑

导读

给告警规则任务级化改造做接口级 E2E 时,踩了一圈环境坑——服务在 JDK 17 下「能起端口、能收请求」,错误全在运行期深处才爆;Git Bash 里 export PATH 是个假动作;curl 带中文直接乱码。每个坑都有实锤报错。这篇把过程沉淀成配方,下次本地起服务测接口照抄即可。

🎧 文章导读

🎵 背景音乐

背景:告警规则任务级化改造 需要在本地完整跑通「建规则 → 上报事件 → 命中规则 → 推弹窗」链路。 Spring Cloud 老项目(Spring Boot 2.5.7 + JDK 8 技术栈),以下配方在这套环境实测通过。

一、服务必须跑 JDK 8(不只是编译)

编译用 JDK 8 谁都记得,但运行也得是 JDK 8。JDK 17 下服务能起端口、能收请求,看起来一切正常,错误全在运行期深处才爆:

服务 JDK 17 症状
admin Feign default method 反射 InaccessibleObjectException(启动即失败)
gateway ClassNotFoundException: javax.xml.bind.DatatypeConverter(jjwt 要 JAXB,登录转发挂)
warning cglib BeanCopier InaccessibleObjectException(接口能通,走到 BeanCopier 才 500)

warning 这个最阴:接口是通的,请求打到具体逻辑才 500,而且 @Transactional 回滚不留脏数据——你会以为是业务 bug,其实是 JDK 模块系统把 cglib 的反射路封了。

启动命令用显式路径,最稳:

1
"E:/jdk/bin/java.exe" -jar service-provider/xxx-service/target/xxx.jar

坑:export PATH="E:/jdk/bin:$PATH" 是假的。
Git Bash 里 E: 后面的冒号会被当成 PATH 分隔符,这一条被拆成 E/jdk/bin 两个无效条目,which java 仍指向 JDK 17。mvn 编译没事是因为它吃 JAVA_HOMEjava -jar 就中招。要用 PATH 就写 Unix 风格:export PATH="/e/jdk/bin:$PATH"

二、最小服务组合与请求路径

不用把全家桶起起来。最小组合:

1
2
3
4
5
flowchart LR
C[curl] --> G[gateway :10001]
G --> A[admin :18200<br/>仅登录用]
G --> W[warning 等被测服务]
W --> M[(MySQL / Redis / Nacos<br/>本机常驻)]

MySQL / Redis / Nacos 本机常驻,不用管。

请求统一打网关,带 devDebug=1

1
curl "http://localhost:10001/{svc}/{path}?devDebug=1" ...

网关日志出现 Route matched ... uri=http://127.0.0.1:<port> 即 devDebug 生效、路由正确。

绕过上游模拟:测 warning 不用起 security / integrate-iot——直接 curl alarmingEvenSink/inputIteration,这就是 security 实际调用的接口,自己造 JSON 事件即可。省掉两个服务,也省掉「上游没数据」这一类干扰。

三、登录拿 token

本地建一个测试号(超管角色 + 绑定园区,建在 building_admin 库;被清了就按 sys_user + sys_user_role + sys_user_park 三表重建)。

密码哈希算法与前端 JsSHA numRounds=2 一致(已实测):

1
2
3
4
SALT_B64=$(printf 'e2etest' | base64)          # 用户名 base64 做盐
R1=$(printf '%s' "${SALT_B64}<本地测试密码>" | openssl dgst -sha512 -hex | sed 's/^.* //')
HASH=$(printf '%s' "$R1" | xxd -r -p | openssl dgst -sha512 -hex | sed 's/^.* //')
# HASH 即登录请求里的 password 字段

注意第二轮:先把第一轮的 hex 字符串 xxd -r -p 转回原始字节,再做 sha512 hex。直接对 hex 字符串再哈希会得到错误结果。

1
2
3
curl -X POST "http://localhost:10001/admin/user/login" -H "Content-Type: application/json" \
-d '{"userName":"e2etest","password":"<HASH>","appId":"biz_admin","endpoint":"pc"}'
# 响应 data.token → 后续请求头 Authorization: Bearer <token>

密码连错 5 次锁号is_enable 置位),别拿没把握的 hash 反复试。先用一次确认 HASH 算对了再进脚本循环。

四、请求体三坑

1. 中文必须文件投递。 Windows Git Bash 里 curl -d 直接带中文,按 GBK 编码发出去,服务端 Jackson 报 Invalid UTF-8 middle byte。JSON 写进 UTF-8 文件,用 curl --data-binary @file 发送,避开终端编码干扰。

2. 裸 Date 字段传 epoch 毫秒。 没有 @JsonFormat 的 Date 字段(如 AlarmingEventData.alarmTime)不收 "yyyy-MM-dd HH:mm:ss"(报「请求参数不正确」),传毫秒数:

1
{ "alarmTime": 1786000430000 }

3. 网关偶发 500 是 DNS。 本机 DNS 偶发超时导致 DnsNameResolverTimeoutException,重试即可,不是代码问题。偶发 500 先重试两次再开始排查,能省半小时。

五、排查看日志顺序

网关 500 时的固定顺序:

  1. 先看 gateway 日志 Route matched 的 uri——确认路由到了正确的服务端口,devDebug 生效。
  2. 再看被测服务日志——路由对了才是服务内部问题。

服务后台启动时输出重定向:

1
"E:/jdk/bin/java.exe" -jar xxx.jar > /tmp/xxx.log 2>&1 &

报错直接过滤:

1
grep -v DEBUG /tmp/xxx.log | tail -50

小结:照抄清单

下次本地起服务做接口级 E2E,按这个顺序:

  1. 显式路径启 JDK 8:"E:/jdk/bin/java.exe" -jar ...(别信 export PATH 的 Windows 路径写法)。
  2. 最小组合:gateway + admin + 被测服务;测下游服务用上游实际调用的接口直接造事件。
  3. 算好登录 HASH(两轮 sha512,第二轮前先转字节),错 5 次锁号,谨慎试。
  4. 请求体:中文走 --data-binary @file,裸 Date 传 epoch 毫秒。
  5. 偶发 500 先重试(DNS);持续 500 按「gateway 路由日志 → 被测服务日志」顺序查。

这套配方最大的价值不是某一条命令,而是把「以为是业务 bug、实际是环境问题」的几类伪装提前点名:JDK 运行期反射、假 PATH、GBK 编码、DNS 抖动。环境坑的特征都是报错位置和真实原因隔着好几层,先排除环境再查业务,顺序不能反。