用 ChatGPT 5.5 辅助排查后端接口问题:从异常日志到定位思路
前言
后端开发中,很多问题并不是“代码写不出来”,而是“问题不好定位”。
尤其是接口联调、线上告警、偶发异常这类场景,经常会遇到下面这些情况:
- 日志很多,但关键信息分散;
- 异常堆栈能看到报错位置,但看不出根因;
- 本地无法稳定复现;
- 测试环境正常,线上偶尔出问题;
- 看起来是空指针,实际可能是鉴权、并发、上下文传递或数据状态问题。
这类问题如果只靠人工一点点翻日志,效率并不高。现在很多开发者会尝试把 ChatGPT 5.5 这类大模型工具引入到开发流程中,用来辅助整理日志、归纳排查方向、生成 checklist、补充测试用例和复盘文档。
需要说明的是,大模型不能替代真实排查,也不能直接作为线上故障结论。它更适合做的是:把混乱的信息结构化,把可能遗漏的方向列出来,帮助开发者更快进入有效排查状态。
本文就以一个比较常见的 Java 后端接口异常为例,看看如何用 ChatGPT 5.5 辅助分析问题。
一、问题场景:订单创建接口偶发 500
假设有一个订单创建接口:
http
POST /api/order/create
接口逻辑比较常规:
- 获取当前登录用户;
- 查询商品信息;
- 判断库存;
- 创建订单;
- 扣减库存;
- 返回订单编号。
最近在联调和压测时发现,这个接口偶尔返回 500。
前端拿到的响应类似:
json
{ "code": 500, "message": "Internal Server Error", "data": null}
服务端日志如下:
text
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)
相关代码简化如下:
java
@PostMapping("/create")public Result<String> create(@RequestBody CreateOrderRequest request) { String orderNo = orderService.createOrder(request); return Result.success(orderNo);}
java
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,调用 getUserId() 时抛出了空指针。
但真实排查时,不能只停留在“加个判空”这一层。
真正要搞清楚的是:为什么用户上下文会为空?
二、不要只问“这段代码哪里错了”
很多人在使用大模型分析代码时,会直接这样问:
text
下面代码报空指针,帮我看看怎么改。
这样问当然也能得到答案,但通常比较浅。模型大概率会给出:
userInfo可能为空;- 需要增加判空;
- 登录状态可能失效;
- 检查 token;
- 加全局异常处理。
这些建议没错,但对定位根因帮助有限。
更好的方式是,把问题背景、异常日志、代码片段和期望输出格式一起给 ChatGPT 5.5。
例如可以这样提问:
text
你是一名 Java 后端问题排查助手。下面是一个订单创建接口的异常日志和简化代码。
请你帮我完成以下任务:1. 只基于我提供的信息整理问题现象;2. 区分“已确认事实”和“可能原因”;3. 给出排查优先级;4. 不要直接下最终结论;5. 输出适合研发排查记录使用。
【异常日志】粘贴异常日志
【相关代码】粘贴代码
【补充背景】接口在并发稍高时更容易出现 500,单次手动调用不一定复现。
这样输入后,模型输出的内容通常会更接近“排查记录”,而不是简单的代码修复建议。
三、先让 ChatGPT 5.5 整理已知信息
一个比较有价值的输出应该类似下面这样:
markdown
## 已确认事实
1. `/api/order/create` 接口偶发返回 500。2. 异常类型为 `NullPointerException`。3. 空指针发生在 `OrderService.createOrder` 方法中。4. 具体位置是调用 `userInfo.getUserId()` 时。5. `userInfo` 来源于 `userContext.getCurrentUser()`。6. 问题在并发稍高时更容易出现,单次调用不一定复现。
## 可能原因
1. 请求未携带有效登录 token。2. token 解析失败,导致用户信息没有写入上下文。3. 鉴权拦截器没有被执行。4. 用户上下文基于 ThreadLocal,但在异步线程中丢失。5. ThreadLocal 设置或清理时机不正确。6. 并发测试时部分请求复用了错误的请求头或压测脚本未正确传参。7. 某些白名单路径配置错误,导致接口绕过鉴权。
## 暂不能确认的部分
1. 不能仅凭 NPE 判断一定是前端没传 token。2. 不能确认一定是 ThreadLocal 问题。3. 不能确认是否和异步调用有关。4. 不能确认是否所有失败请求都缺少用户信息。
这个过程看似简单,但实际很有帮助。
排查问题最怕的是把“可能原因”直接当成“最终结论”。
ChatGPT 5.5 在这里的作用,是帮我们先把信息分层:
- 哪些是日志已经证明的;
- 哪些只是合理猜测;
- 哪些还需要继续补证据。
这一步做好之后,后续排查会清晰很多。
四、生成一条更靠谱的排查路径
接下来可以继续让模型基于已有信息生成排查步骤:
text
请基于上面的事实和可能原因,给出一个后端接口排查路径。
要求:1. 从最容易验证、最高优先级开始;2. 每一步说明要看什么;3. 每一步说明目的;4. 不超过 8 步;5. 覆盖 token、鉴权拦截器、ThreadLocal、并发复现、日志补充几个方向。
可能得到如下结果:
markdown
## 排查路径
### 1. 确认失败请求是否携带 token
- 看什么:网关日志、access log、请求 Header。- 目的:确认失败请求是否缺少 Authorization 信息。
### 2. 检查 token 解析是否成功
- 看什么:token 是否过期、格式是否正确、解析出的 userId 是否为空。- 目的:判断用户信息是否在鉴权阶段已经丢失。
### 3. 确认鉴权拦截器是否执行
- 看什么:Filter、Interceptor、网关鉴权日志。- 目的:确认该接口是否经过统一鉴权流程。
### 4. 检查 UserContext 的实现方式
- 看什么:是否使用 ThreadLocal 保存当前用户。- 目的:判断用户信息是否依赖当前请求线程。
### 5. 检查 ThreadLocal 设置和清理时机
- 看什么:是否在请求开始时 set,请求结束后 remove。- 目的:排查上下文未写入、提前清理或线程复用污染问题。
### 6. 检查是否存在异步调用
- 看什么:是否使用 @Async、线程池、CompletableFuture。- 目的:判断用户上下文是否在跨线程时丢失。
### 7. 并发压测复现
- 看什么:并发请求下 userInfo 为 null 的比例、失败请求特征。- 目的:确认问题是否与并发、线程复用或压测脚本有关。
### 8. 增加业务层防御和关键日志
- 看什么:userInfo 为空时返回明确错误码,同时记录 requestId。- 目的:避免 NPE 暴露为 500,并提升后续定位效率。
这类 checklist 很适合放到排查文档里,也方便团队成员同步进展。
五、代码层面先做防御,而不是直接 NPE
从代码质量角度看,下面这行确实有风险:
java
Long userId = userInfo.getUserId();
如果 userInfo 为空,接口会直接抛 NPE,并返回 500。
更合理的做法是把“未登录”或“登录状态异常”和“系统异常”区分开。
可以改成:
java
public String createOrder(CreateOrderRequest request) { UserInfo userInfo = userContext.getCurrentUser(); if (userInfo == null || userInfo.getUserId() == null) { throw new UnauthorizedException("用户未登录或登录状态已失效"); }
if (request == null || request.getProductId() == null) { throw new BizException("商品参数不能为空"); }
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();}
再配合全局异常处理:
java
@RestControllerAdvicepublic 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;
- 业务参数问题返回 400;
- 真正未知异常才返回 500;
- 前端和后端都能更清楚地区分问题类型。
不过要注意,这只是防御性改造。
如果按业务逻辑该接口必须登录,那么 userInfo 为空仍然是一个需要继续追查的问题。
六、重点关注 ThreadLocal 用户上下文
很多 Java Web 项目会使用 ThreadLocal 保存当前用户信息,例如:
java
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(); }}
在拦截器中设置用户信息:
java
@Componentpublic class AuthInterceptor implements HandlerInterceptor {
@Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { String token = request.getHeader("Authorization");
UserInfo userInfo = tokenService.parseToken(token); UserContext.set(userInfo);
return true; }
@Override public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) { UserContext.clear(); }}
这种写法比较常见,但需要注意几个问题。
1. 请求结束后必须 clear
Web 容器通常使用线程池处理请求。
如果 ThreadLocal 不及时清理,线程被复用后,可能出现上下文残留。
正确做法是在请求结束后执行:
java
UserContext.clear();
如果项目中有多个 Filter、Interceptor,还要确认异常情况下也能清理。
2. 异步线程拿不到原线程上下文
例如:
java
CompletableFuture.runAsync(() -> { UserInfo userInfo = UserContext.getCurrentUser(); // 这里很可能拿到 null});
因为 ThreadLocal 默认只在当前线程有效,切换线程后不会自动传递。
如果业务确实需要异步使用用户信息,更推荐显式传参:
java
Long userId = UserContext.getCurrentUser().getUserId();
CompletableFuture.runAsync(() -> { orderLogService.saveCreateLog(userId);});
不要在异步线程中继续依赖当前请求线程的 ThreadLocal。
3. 拦截器路径可能没有覆盖接口
需要检查拦截器配置:
java
@Overridepublic void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(authInterceptor) .addPathPatterns("/api/**") .excludePathPatterns("/api/login", "/api/public/**");}
如果路径配置不准确,可能会导致某些接口绕过鉴权。
比如订单接口实际路径是:
text
/order/api/create
但拦截器只拦截:
tex
/api/**
就可能出现鉴权没有执行的情况。
七、让 ChatGPT 5.5 帮忙设计日志点
偶发问题最怕日志不够。
如果日志里只有一行 NPE,那么下次再出现问题,还是很难继续分析。
可以让 ChatGPT 5.5 帮你设计日志点:
text
针对订单创建接口偶发 userInfo 为空的问题,请帮我设计需要补充的关键日志。
要求:1. 不打印完整 token;2. 能关联一次请求;3. 覆盖网关、拦截器、业务层;4. 避免泄露敏感信息;5. 输出字段清单。
可以整理出如下日志方案。
1. 网关层日志
建议记录:
text
requestIdpathmethodclientIphasAuthorizationHeadertokenHashtargetService
注意不要打印完整 token。
如果要关联 token,可以只打印 hash 后的短标识。
2. 鉴权层日志
建议记录:
text
requestIdpathtokenExiststokenParseSuccessuserIdauthResult
这样可以确认:
- token 是否存在;
- token 是否解析成功;
- 是否拿到了 userId;
- 是否放行请求。
3. 业务入口日志
建议记录:
text
requestIduserInfoExistsuserIdproductId
当 userInfo 为空时,可以记录一条 warn 日志:
java
if (userInfo == null) { log.warn("create order userInfo is null, requestId={}, productId={}", RequestIdHolder.get(), request.getProductId()); throw new UnauthorizedException("用户未登录或登录状态已失效");}
4. 异常日志
异常日志需要保留完整堆栈,同时带上 requestId:
java
log.error("create order failed, requestId={}, productId={}", requestId, request.getProductId(), e);
这样后续就可以通过 requestId 串起网关、鉴权、业务层日志。
八、并发场景下还要检查库存扣减
虽然当前日志显示问题是 userInfo 为空,但订单创建接口通常还会有另一个高频问题:库存超卖。
原代码里是:
java
Product product = productMapper.selectById(request.getProductId());if (product.getStock() <= 0) { throw new BizException("库存不足");}
orderMapper.insert(order);
productMapper.decreaseStock(product.getId());
如果多个请求同时进来,可能都读到 stock > 0,然后都去扣减库存。
更稳妥的方式是使用带条件的库存扣减:
sql
UPDATE productSET stock = stock - 1WHERE id = #{productId} AND stock > 0
Java 代码中判断影响行数:
java
int rows = productMapper.decreaseStockIfEnough(product.getId());if (rows == 0) { throw new BizException("库存不足");}
这样可以避免并发下库存被扣成负数。
为什么这里也要提库存?
因为接口偶发 500、并发下异常这类问题,经常不止一个原因。排查 userInfo 的同时,也应该顺手检查核心业务链路中的并发安全点。
九、让 ChatGPT 5.5 生成测试用例清单
除了排查路径,大模型还适合帮忙补测试用例。
可以输入:
text
请根据订单创建接口的逻辑,生成一份测试用例清单。
要求覆盖:1. 正常创建订单;2. 未登录;3. token 过期;4. 商品不存在;5. 库存不足;6. 并发扣减库存;7. userInfo 为空;8. productId 为空。
输出可以整理为:
| 用例 | 输入条件 | 预期结果 |
|---|---|---|
| 正常创建订单 | token 有效,productId 有效,库存充足 | 返回订单号 |
| 未登录 | 不携带 token | 返回 401 |
| token 过期 | 携带过期 token | 返回 401 |
| 商品不存在 | productId 不存在 | 返回 400 |
| 库存不足 | 商品库存为 0 | 返回 400 |
| 并发扣减库存 | 多线程同时下单 | 不出现超卖 |
| userInfo 为空 | 模拟上下文未设置 | 返回 401,不抛 NPE |
| productId 为空 | 请求参数缺失 | 返回 400 |
这类内容手写也不难,但模型可以更快生成第一版,然后开发者再根据项目实际情况补充。
十、一个比较实用的 Prompt 模板
如果想用 ChatGPT 5.5 辅助排查后端接口问题,可以参考下面这个模板:
text
你是一名有经验的 Java 后端问题排查助手。请根据我提供的信息,帮我分析接口异常。
【接口背景】接口名称:接口路径:主要逻辑:
【异常现象】1.2.3.
【异常日志】粘贴日志
【相关代码】粘贴核心代码
【补充信息】是否偶发:是否与并发有关:是否本地可复现:最近是否改动过:
【输出要求】1. 先整理已确认事实;2. 再列出可能原因;3. 区分事实和推测;4. 给出排查优先级;5. 每一步说明要看什么、为什么看;6. 不要直接下最终结论;7. 如信息不足,请列出还需要补充的数据。
这个模板的关键点是:不要让模型直接“猜原因”,而是让它参与“整理和拆解问题”。
十一、使用大模型排查问题时的注意事项
1. 不要把模型结论当最终结论
模型能根据上下文给出合理猜测,但线上问题必须靠证据闭环。
比如它说“可能是 ThreadLocal 丢失”,你还需要验证:
- 是否真的使用 ThreadLocal;
- 是否存在异步线程;
- 是否在失败请求中没有设置上下文;
- 是否有日志证明这一点。
2. 不要粘贴敏感信息
排查接口问题时,不建议直接粘贴:
- 完整 token;
- 用户手机号;
- 身份证号;
- 数据库密码;
- 内部密钥;
- 真实生产用户数据。
必要时应先脱敏。
3. 给模型的信息越结构化,输出越有用
相比这样问:
text
接口报错了,帮我看看。
更推荐这样问:
text
这是异常日志,这是代码,这是复现条件,这是我已经排查过的内容,请帮我整理下一步排查路径。
信息越完整,模型越不容易泛泛而谈。
4. 让模型输出 checklist,而不是直接输出结论
对于复杂问题,最有用的不是一句“根因是 XXX”,而是一份可以逐项验证的 checklist。
总结
ChatGPT 5.5 用在后端开发中,比较适合处理这类事情:
- 整理异常日志;
- 分析堆栈信息;
- 区分事实和推测;
- 生成排查路径;
- 补充日志点;
- 设计测试用例;
- 整理复盘文档。
但它不适合直接替代开发者做最终判断。
以本文的订单创建接口为例,日志表面上是 userInfo 为空导致 NPE,但真正排查时需要继续确认 token、鉴权拦截器、ThreadLocal、异步线程、并发压测和日志链路等多个方向。
合理的使用方式是:让 ChatGPT 5.5 帮你把混乱信息整理成结构化排查清单,然后由开发者结合真实日志、监控和复现实验逐步验证。这样既能提高排查效率,也能避免因为过早下结论而走偏。
更多推荐

所有评论(0)