文章摘要:本文以 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、鉴权、并发等方向;
- 整理压测复现思路;
- 最后生成问题复盘文档。
但最终结论仍然要靠真实证据确认。
如果把它当成一个“排查思路整理助手”,而不是“线上故障裁判”,它在日常开发中的价值会更稳定,也更符合真实工程场景。
361

被折叠的 条评论
为什么被折叠?



