文章摘要:本文以 ChatGPT 5.5 为例,介绍如何在后端接口排查中利用大模型辅助整理日志、分析异常堆栈、区分已确认事实与可能原因,并围绕订单创建接口偶发 500、userInfo 为空、ThreadLocal 上下文丢失、鉴权链路异常等问题,给出排查路径、日志补充建议和防御性代码思路。同时提到可通过 KULAAI 快速访问 ChatGPT、Claude、Gemini 等模型,降低部署和调试成本。
最近在做接口联调和线上问题排查时,一个很明显的感受是:后端开发真正耗时间的,往往不是写某一段业务代码,而是在一堆日志、异常堆栈、接口参数、数据库状态之间来回切换。尤其是线上偶发问题,日志看起来都有信息,但要把它们串成一条完整的定位路径,还是很费脑子。现在很多开发者会把 ChatGPT、Claude、Gemini 这类大模型工具放进日常开发流程里,用来辅助分析日志、整理排查步骤、生成测试用例和复盘文档。
我自己也对比过自研部署、开源 UI 和一些第三方聚合平台。实际用下来,如果只是想在国内环境里快速试用多种模型,而不是花时间折腾部署和接口配置,KULAAI(https://ouai.me)这类一站式集成工具会比较省事。它集成了 Gemini、ChatGPT、Claude 等主流模型,适合个人日常测试,也适合小项目快速验证想法。本文主要围绕 ChatGPT 5.5 展开,聊一个更贴近日常开发的场景:如何用它辅助分析后端接口异常,把零散报错整理成可执行的排查路径。
需要提前说明一点:这里说的“辅助排查”,不是让 ChatGPT 5.5 直接替你判断线上故障原因。线上问题最终还是要靠日志、监控、链路追踪、数据库状态和复现实验来确认。大模型更适合做的是:帮你整理信息、补全排查清单、发现容易遗漏的方向,并把一堆杂乱内容转成结构化分析。
一、问题背景:一个接口偶发 500
假设现在有一个很常见的后端接口:
http
POST /api/order/create接口功能是创建订单,流程大致如下:
- 校验用户登录状态;
- 查询商品库存;
- 创建订单记录;
- 扣减库存;
- 写入订单日志;
- 返回订单号。
最近联调时发现,这个接口偶尔返回 500。不是每次都失败,而是在并发稍微高一点的时候更容易出现。
接口返回:
json
{ "code": 500, "message": "Internal Server Error", "data": null }服务端日志里可以看到类似内容:
2026-01-15 10:23:41.231 ERROR [order-service] c.e.order.controller.OrderController : create order failed java.lang.NullPointerException: Cannot invoke "com.example.user.UserInfo.getUserId()" because "userInfo" is null at com.example.order.service.OrderService.createOrder(OrderService.java:86) at com.example.order.controller.OrderController.create(OrderController.java:42)相关代码简化如下:
@PostMapping("/create") public Result<String> create(@RequestBody CreateOrderRequest request) { String orderNo = orderService.createOrder(request); return Result.success(orderNo); }public String createOrder(CreateOrderRequest request) { UserInfo userInfo = userContext.getCurrentUser(); Long userId = userInfo.getUserId(); Product product = productMapper.selectById(request.getProductId()); if (product.getStock() <= 0) { throw new BizException("库存不足"); } Order order = new Order(); order.setUserId(userId); order.setProductId(product.getId()); order.setAmount(product.getPrice()); orderMapper.insert(order); productMapper.decreaseStock(product.getId()); return order.getOrderNo(); }从堆栈看,问题似乎很直接:userInfo是null。
但真实开发里,不能看到 NPE 就直接改一行判空了事。更关键的问题是:为什么当前接口里拿不到用户信息?
二、不要直接问“这是什么问题”
很多人使用大模型时会这样问:
下面代码报空指针,帮我看看原因。这种问法不是不能用,但模型大概率会回答:
userInfo为空;- 需要判空;
- 检查登录态;
- 检查 token;
- 增加异常处理。
这些建议没错,但比较浅。
更好的问法是,把日志、代码、背景和你希望的输出形式一起给它。
可以这样问 ChatGPT 5.5:
你是一名 Java 后端问题排查助手。下面是一个订单创建接口的异常日志和简化代码。 请你帮我完成: 1. 只基于已给信息整理问题现象; 2. 区分“已确认事实”和“可能原因”; 3. 给出排查优先级; 4. 不要直接下最终结论; 5. 输出适合研发排查记录使用。 【异常日志】 粘贴日志 【相关代码】 粘贴代码 【补充背景】 接口在并发稍高时更容易出现 500,单次手动调用不一定复现。这样输入后,ChatGPT 5.5 的输出通常会更接近排查记录,而不是简单给一句“加判空”。
三、让 ChatGPT 5.5 先整理事实
比较理想的输出应该类似这样:
markdown
## 已确认事实 1. `/api/order/create` 接口偶发返回 500。 2. 异常类型为 `NullPointerException`。 3. 空指针发生在 `OrderService.createOrder` 方法中。 4. 具体位置是调用 `userInfo.getUserId()` 时,`userInfo` 为 null。 5. `userInfo` 来源于 `userContext.getCurrentUser()`。 6. 问题在并发稍高时更容易出现,单次调用不一定复现。 ## 可能原因 1. 当前请求未正确携带登录 token。 2. token 解析失败,导致用户上下文未写入。 3. 用户上下文使用了 ThreadLocal,但在线程复用或异步场景下没有正确设置。 4. 网关或拦截器在部分请求中没有执行鉴权逻辑。 5. 并发场景下存在上下文污染或清理时机不正确。 6. 测试环境存在部分请求绕过登录态的情况。 ## 暂不能直接确认的部分 1. 不能仅凭 NPE 判断是前端未传 token。 2. 不能确认一定是 ThreadLocal 问题,需要查看 `userContext` 实现。 3. 不能确认是否与异步线程有关,需要查看调用链。 4. 不能确认是否所有失败请求都缺少 token,需要结合网关日志或 access log。这个结构就比“加个 if 判断”有价值多了。
因为实际排查线上问题时,第一步不是急着改代码,而是先搞清楚:
- 确认了什么;
- 猜测了什么;
- 哪些证据还缺;
- 下一步该查哪里。
四、继续让它生成排查路径
接下来可以继续问:
请基于上面的事实和可能原因,给出一个后端接口排查路径。 要求: 1. 从最容易验证、最高优先级开始; 2. 每一步说明要看什么; 3. 每一步说明目的; 4. 不超过 8 步; 5. 包含日志、鉴权、ThreadLocal、并发复现几个方向。输出可以整理成这样:
markdown
## 排查路径 ### 1. 确认失败请求是否携带 token - 看什么:access log、网关日志、请求 Header。 - 目的:确认失败请求是否缺少 Authorization 信息。 ### 2. 查看鉴权拦截器是否执行 - 看什么:登录拦截器、Filter、Interceptor 的日志。 - 目的:确认请求是否经过统一鉴权流程。 ### 3. 检查 token 解析结果 - 看什么:token 是否过期、解析是否异常、用户 ID 是否为空。 - 目的:确认是否因为 token 无效导致用户上下文未写入。 ### 4. 检查 UserContext 实现 - 看什么:是否基于 ThreadLocal 保存用户信息。 - 目的:确认用户上下文是否依赖当前线程。 ### 5. 检查 ThreadLocal 设置和清理时机 - 看什么:是否在请求开始时 set,在请求结束时 remove。 - 目的:避免上下文残留或被提前清理。 ### 6. 检查是否存在异步调用 - 看什么:是否使用 `@Async`、线程池、CompletableFuture。 - 目的:确认用户上下文是否跨线程丢失。 ### 7. 压测复现并增加关键日志 - 看什么:并发请求下 userInfo 为 null 的比例。 - 目的:判断问题是否与并发、线程复用有关。 ### 8. 增加接口防御性处理 - 看什么:userInfo 为空时是否返回明确的 401/登录失效。 - 目的:避免 NPE 暴露为 500,同时提升问题可观测性。这个排查顺序比较适合真实项目。
它不会一上来就认定“前端没传 token”,也不会直接把问题归到 ThreadLocal,而是一步步补证据。
五、补充一个更合理的接口防御写法
从代码上看,这里确实应该避免直接调用:
Long userId = userInfo.getUserId();可以先做基本防御:
public String createOrder(CreateOrderRequest request) { UserInfo userInfo = userContext.getCurrentUser(); if (userInfo == null || userInfo.getUserId() == null) { throw new UnauthorizedException("用户未登录或登录状态已失效"); } Long userId = userInfo.getUserId(); Product product = productMapper.selectById(request.getProductId()); if (product == null) { throw new BizException("商品不存在"); } if (product.getStock() <= 0) { throw new BizException("库存不足"); } Order order = new Order(); order.setUserId(userId); order.setProductId(product.getId()); order.setAmount(product.getPrice()); orderMapper.insert(order); productMapper.decreaseStock(product.getId()); return order.getOrderNo(); }同时配合全局异常处理:
@RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(UnauthorizedException.class) public Result<Void> handleUnauthorized(UnauthorizedException e) { return Result.fail(401, e.getMessage()); } @ExceptionHandler(BizException.class) public Result<Void> handleBizException(BizException e) { return Result.fail(400, e.getMessage()); } @ExceptionHandler(Exception.class) public Result<Void> handleException(Exception e) { // 这里应记录完整异常堆栈 return Result.fail(500, "系统异常,请稍后重试"); } }这样做的好处是:
- 未登录问题返回 401,而不是 500;
- 业务异常和系统异常区分开;
- 前端能根据状态码做明确处理;
- 后端日志也更容易定位问题。
不过要注意:这只能防止接口继续抛 NPE,并不等于根因已经解决。
如果用户上下文不应该为空,那仍然要继续查鉴权链路和上下文传递。
六、重点检查 ThreadLocal 场景
很多 Java Web 项目会用 ThreadLocal 保存当前登录用户,例如:
public class UserContext { private static final ThreadLocal<UserInfo> USER_HOLDER = new ThreadLocal<>(); public static void set(UserInfo userInfo) { USER_HOLDER.set(userInfo); } public static UserInfo getCurrentUser() { return USER_HOLDER.get(); } public static void clear() { USER_HOLDER.remove(); } }拦截器中设置:
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { String token = request.getHeader("Authorization"); UserInfo userInfo = tokenService.parse(token); UserContext.set(userInfo); return true; }请求结束后清理:
public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { UserContext.clear(); }这种写法本身很常见,但有几个坑:
1. 没有及时 clear
如果请求结束后没有remove(),线程池复用时可能出现用户上下文残留。
这类问题更隐蔽,甚至可能造成数据串用户。
2. 异步线程拿不到上下文
比如:
CompletableFuture.runAsync(() -> { UserInfo userInfo = UserContext.getCurrentUser(); // 这里可能是 null });因为 ThreadLocal 只绑定当前线程,换了线程就拿不到原来的值。
3. 拦截器没有覆盖所有路径
有些接口路径可能被配置为白名单,或者网关转发时路径变化,导致鉴权逻辑没有执行。
4. token 解析异常被吞掉
如果 token 解析失败后没有中断请求,而是继续往下走,就可能导致业务层拿到 null。
这几个点都很适合让 ChatGPT 5.5 帮忙整理成 checklist,然后逐项验证。
七、让 ChatGPT 5.5 帮你补日志点
排查偶发问题时,最怕日志里只有一句 NPE,没有上下文。
可以继续问:
针对这个接口偶发 userInfo 为空的问题,请帮我设计需要补充的关键日志点。 要求: 1. 不打印完整 token; 2. 能关联单次请求; 3. 包含网关、拦截器、业务层; 4. 注意日志不要泄露敏感信息。可以得到类似建议:
markdown
## 建议日志点 ### 1. 网关层 - requestId - path - method - 是否存在 Authorization Header - token 前 6 位 hash 或脱敏标识 - 转发目标服务 ### 2. 鉴权拦截器 - requestId - token 是否存在 - token 解析是否成功 - 解析出的 userId - 是否写入 UserContext ### 3. 业务层入口 - requestId - productId - UserContext 中 userInfo 是否为空 - userId 是否为空 ### 4. 异常日志 - requestId - 接口路径 - 异常类型 - 堆栈 - 当前线程名对应代码中可以使用 MDC 记录 requestId:
@Component public class RequestIdFilter implements Filter { @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { String requestId = UUID.randomUUID().toString().replace("-", ""); MDC.put("requestId", requestId); try { chain.doFilter(request, response); } finally { MDC.remove("requestId"); } } }日志格式中带上:
[%X{requestId}] %-5level %logger - %msg%n这样一次请求从网关、拦截器到业务层,都能串起来。
八、让它生成压测复现思路
如果问题只在并发稍高时出现,就不能只靠手动点接口。
可以让 ChatGPT 5.5 生成一个简单的压测方案:
请给出一个针对该接口的并发复现方案。 要求: 1. 使用 JMeter 或 curl 都可以; 2. 重点观察 userInfo 为空的问题; 3. 给出需要记录的指标; 4. 不要求复杂压测,只用于问题复现。它可能会给出这样的方案:
markdown
## 并发复现方案 ### 测试目标 验证 `/api/order/create` 在并发请求下是否更容易出现 `userInfo == null`。 ### 请求准备 - 使用同一个有效 token; - 使用多个不同商品 ID; - 保证测试商品库存充足; - 每个请求带上 requestId。 ### 并发设置 - 线程数:10、30、50 逐步增加; - 循环次数:每个线程 100 次; - 间隔时间:可设置 100ms; - 观察错误率变化。 ### 重点记录 - 总请求数; - 500 数量; - 401 数量; - userInfo 为空次数; - token 解析失败次数; - 平均响应时间; - 失败请求 requestId。 ### 判断方向 - 如果 401 增多:可能是 token 校验或登录态问题; - 如果 500 仍然出现:说明业务层异常处理仍不完整; - 如果 userInfo 为空集中在异步流程:重点检查上下文传递; - 如果并发越高越明显:重点检查线程池、ThreadLocal 或共享变量。这类输出不是最终方案,但能快速给你一个复现框架。
九、ChatGPT 5.5 在后端开发里适合做什么
结合这个例子,我觉得 ChatGPT 5.5 比较适合做这些事情:
1. 日志整理
把 N 段日志合并成:
- 问题现象;
- 异常位置;
- 可能影响范围;
- 待确认问题。
2. 排查清单生成
例如:
- 接口 500 排查清单;
- Redis 缓存穿透排查清单;
- MySQL 慢查询排查清单;
- MQ 消息堆积排查清单。
3. 代码风险点提示
它可以帮你指出一些常见问题:
- 空指针;
- 事务边界;
- 并发安全;
- 资源未关闭;
- 异常被吞;
- 日志不完整。
4. 单元测试用例补充
例如针对订单接口,可以让它生成测试用例:
- 未登录;
- 商品不存在;
- 库存不足;
- 正常下单;
- 并发下单;
- 重复提交。
5. 复盘文档生成
一次线上问题处理完,可以把排查过程丢给它,让它整理成:
- 问题背景;
- 影响范围;
- 时间线;
- 根因分析;
- 修复方案;
- 预防措施。
十、但不要让它替你拍板
需要强调的是,大模型在后端排查里也有明显边界。
它不适合:
- 在缺少日志时直接判断根因;
- 替你确认线上数据库状态;
- 替你判断某次发布是否一定导致问题;
- 在没看完整调用链时给最终结论;
- 直接生成未经验证的生产修复代码。
尤其是线上故障,不能因为模型说“可能是 ThreadLocal”就直接改生产逻辑。
正确做法应该是:让它帮你整理假设,然后你用日志、监控和复现实验去验证。
十一、我常用的 Prompt 模板
如果你想用 ChatGPT 5.5 辅助后端接口排查,可以直接套这个模板:
你是一名 Java 后端问题排查助手。下面是接口异常信息,请帮我整理排查思路。 【接口背景】 接口路径: 接口功能: 技术栈: 是否经过网关: 是否需要登录: 【异常现象】 1. 2. 3. 【异常日志】 粘贴完整异常日志 【相关代码】 粘贴 Controller / Service / Mapper / 拦截器代码 【补充信息】 是否偶发: 是否与并发有关: 最近是否发布: 是否有监控异常: 【输出要求】 1. 先整理已确认事实; 2. 再列可能原因; 3. 给出排查优先级; 4. 每一步说明要看什么日志或证据; 5. 不要直接下最终结论; 6. 如果信息不足,请列出需要补充的内容。这个模板的核心是:让模型帮你“拆问题”,而不是让它直接“猜答案”。
总结
ChatGPT 5.5 用在后端开发里,比较实用的位置不是替你写几行简单代码,而是辅助处理那些信息量大、上下文多、排查链路长的问题。
以接口偶发 500 为例,它可以帮你:
- 从日志中提取关键事实;
- 区分已确认现象和可能原因;
- 生成排查优先级;
- 补充日志设计;
- 梳理 ThreadLocal、鉴权、并发等方向;
- 整理压测复现思路;
- 最后生成问题复盘文档。
但最终结论仍然要靠真实证据确认。
如果把它当成一个“排查思路整理助手”,而不是“线上故障裁判”,它在日常开发中的价值会更稳定,也更符合真实工程场景。