1.成果展示
Nacos 配 Webhook + 卡片内 Apifox 风格 curl(含 query / body / 响应 JSON)
左图apifox右图飞书群

2. 请求体有 Content-Type 却无 body的解决方法
text
客户端(Apifox) → Gateway(Netty) → api-app(Tomcat) → GlobalExceptionHandler → 飞书
| 断点 | 现象 | 处理 |
|---|---|---|
| Gateway | GET/DELETE 带 body 时默认路由可能不转发 body | ReproduceBodyPreserveGlobalFilter:join body 再装饰 getBody() + Content-Length |
| Tomcat | GET 的 InputStream 常读不到 body(业务也不读) |
网关读完后写内网头 X-Brain-Reproduce-Body-B64(≤24KB);api-app 解码写入 TraceContext |
| 双层 Wrapper | RequestTrace 内层有缓存,PlaintextDebugCapture 再包一层空外壳 |
不再往ContentCachingRequestWrapper;readCachedBody 沿链向内找;优先 TraceContext 字节快照 |
3. Apifox body 类型 → curl 片段
| Body | curl |
|---|---|
| Query | 已在 URL ?key=value |
| JSON | --data-raw '{"k":1}'(紧凑 JSON) |
| x-www-form-urlencoded | --data-raw 'a=1&b=2' |
| multipart/form-data | 解析 boundary → 多行 --form 'name="value"';文件用 --form 'f=@文件名' |
实现:ReproduceRequestBodyCapture(字节+Content-Type) → MultipartFormParser / ApifoxCurlBodyAppender。
预热:RequestTraceServletFilter 对「有 Content-Length / chunked / 有 Content-Type」尝试 readAllBytes();multipart 必须用字节,别先转 String。
4. 飞书展示层
| 问题 | 原因 | 处理 |
|---|---|---|
Accept: */* 变 Accept: / |
卡片 Markdown 吃掉 * |
推送前 FeishuMarkdownEscaper(\*) |
缺 Connection |
经网关常无此头 | 缺省补 keep-alive |
缺 --form |
见 §2 body 未到 api-app | 网关 relay + multipart 解析 |
对外 URL:X-Forwarded-Host / X-Forwarded-Proto 拼 xxx.xxxx.com,别用内网。
5. 避坑和安全设计
- 发 dev不要用tar命令覆盖 ECS的配置文件
.env(不然会把NACOS_PASSWORD冲成nacos→ 全链 403)。 reproduceBlock可含 Authorization (仅飞书 critical,不进security_log明文)。