① 基本信息
| 问题描述 | 点击保存时报错,但是项目实际已经创建,内网侧水新账号看不到相关信息 |
|---|---|
| 一级模块 | 项目信息填报 |
| 二级模块 | 项目基本信息 |
| 涉及类型 | 中压分布式新能源 |
| 台账标注区域 | 五区 |
| 严重性 | 严重 |
| 是否阻断流程 | 是 |
| 当前状态 | 2.1.待开发 |
| 研发处理人 | 冷柏烨 |
| 处理状态 | 已调整待验证 |
② 问题截图
③ 旧代码涉及的项目文件及行数(backup730/730backup 分支)
xnybw5b / src/main/java/cn/csg/so/oms/in/newenergy/logrecording/OperationLogAspect.java
第 34-71 行(controllerAround 环绕通知,finally 块内调用 insertLogByEntity)
xnybw5b / src/main/java/cn/csg/so/oms/in/newenergy/project/controller/NewNePjController.java
第 89-96 行(addOrUpdatePro 接口,标注 @OperationLog 注解)
④ 代码现状与修复方案
"新增/保存项目"接口(addOrUpdatePro)标注了 @OperationLog 操作日志注解,实际业务逻辑之外,还会被 OperationLogAspect 这个全局 AOP 切面环绕:
@PostMapping("/addOrUpdatePro")
@ApiOperation(value = "新增或更新项目", notes = "新增或更新项目信息")
@OperationLog(module = OperationModule.XNYBW_XMGL, type = OperationType.INSERT) // 第92行:会被OperationLogAspect环绕拦截
public Object addOrUpdatePro(@RequestBody ProjectVo vo){
...
return projectImplFacade.addProject(vo); // 真正的建项目逻辑,此处执行完即已提交入库
}
OperationLogAspect.controllerAround() 的关键问题在于:记录操作日志的 insertLogByEntity(...) 调用放在了 finally 块里,且没有自己的 try/catch 兜底:
try {
result = joinPoint.proceed(); // 第55行:真正执行addOrUpdatePro,项目已创建并提交事务
operationResult = "成功";
return result;
} catch (Throwable e) {
...
throw e;
} finally {
watch.stop();
Integer timeCost = Math.toIntExact(watch.getTotalTimeMillis());
operationLogAppService.insertLogByEntity(args, result, date, request, timeCost, operationResult, errorMsg, operationLog); // 第69行:finally里再抛异常会覆盖掉已经return的正常结果!
}
这是一个经典的 Java
finally 覆盖异常陷阱:joinPoint.proceed()(也就是真正的建项目逻辑)此时已经执行完毕并成功提交数据库事务,方法本应正常 return result;但 finally 块里紧接着调用的 insertLogByEntity(...)——用于把本次请求的入参、返回值、耗时等序列化后写入操作日志表——如果这一步本身抛出异常(例如把 ProjectVo 这种带多层嵌套集合的大对象做 JSON 序列化时失败、或日志表写入超时/唯一键冲突等),这个异常会从 finally 块里向外抛出,直接盖过前面 try 块里已经成功执行的 return,导致整个 HTTP 请求最终以 500 错误返回给前端,页面就弹出了"运行时异常,请联系管理员!"——但此时项目其实早已经创建成功并落库,这与截图1(报错)和截图2(项目确实存在于列表中)完全吻合。
结合台账"内网侧水新账号看不到相关信息"的补充描述:由于该请求最终以异常告终,凡是依赖"接口正常返回成功"这个信号才触发的后续动作(例如给内网水新审核账号生成待办/通知记录,如果这类逻辑是在 addProject(vo)返回之后、或在同一个被 AOP 包裹的调用链后半段才执行),也会因为最终抛出的异常而被跳过或前端误判为"未完成",从而出现内网侧收不到通知的连带现象。
建议修复方式:给 finally 块里的日志记录调用单独加 try/catch,绝不能让"记日志"这种辅助性动作的失败影响主业务的正常返回:
} finally { watch.stop(); Integer timeCost = Math.toIntExact(watch.getTotalTimeMillis()); operationLogAppService.insertLogByEntity(args, result, date, request, timeCost, operationResult, errorMsg, operationLog); } } finally { watch.stop(); Integer timeCost = Math.toIntExact(watch.getTotalTimeMillis()); try { operationLogAppService.insertLogByEntity(args, result, date, request, timeCost, operationResult, errorMsg, operationLog); } catch (Throwable logEx) { // 记录日志失败绝不能影响主业务已经拿到的正常结果,仅记录,不再向外抛出 LOGGER.error("操作日志记录失败(不影响主业务结果)--------- 》{}.{},异常信息:{}", className, methodName, logEx); } }