今天继续处理昨天那个"场景数据异步生成"的需求。昨天已经把主体代码写完了,接口也初步跑通了,所以今天主要围绕代码检查、日志补充、完整链路验证和提交前问题修复展开。
这个需求不是简单把一个接口改成异步就结束了。因为场景创建背后会初始化很多业务数据,比如场景基础参数、预测计划数据、申报数据、约束数据、碳价数据等。以前这些逻辑都在一次接口调用里同步执行,前端点击创建后需要一直等待后端处理完成。现在改成异步之后,请求返回变快了,但后端也必须把"创建中、创建失败、已完成、未完成"这些状态维护清楚。
一、重新检查异步创建的完整链路
早上我先把昨天写的代码从头到尾重新看了一遍。因为这次改动涉及的类比较多,而且有线程池、事务、状态刷新、数据校验这些逻辑,所以我不太敢只看接口能不能返回成功。
调整后的创建流程可以简单理解为:
前端调用新增场景接口;
后端先插入一条场景主记录,并把状态设置为"创建中";
接口立即返回,前端列表上能先看到这条新场景;
后端把真正耗时的数据初始化任务提交到专用线程池;
后台线程依次生成各类场景明细数据;
明细数据生成完成后,再执行场景完整性校验;
根据校验结果,把状态更新为"已完成"或"未完成";
如果后台执行过程中出现异常,则把状态更新为"创建失败"。
这里比较容易出问题的地方,是异步任务已经脱离了原来的请求线程。以前同步接口里如果抛异常,前端马上能看到失败结果;现在后台任务失败时,接口早就返回了,所以必须通过状态字段把失败结果表现出来。如果异常发生后状态没有更新,场景就可能一直卡在"创建中"。
所以我重点看了两个地方:一个是后台任务正常完成后,最终状态是否能落库;另一个是后台任务异常时,失败状态是否能独立更新。这里还有一个事务上的细节:明细数据初始化需要放在事务里,保证某一步失败时可以回滚本次生成的数据;但"创建失败"这个状态不能和明细初始化放在同一个事务里,否则初始化逻辑一旦回滚,失败状态也会被回滚。
二、补充日志验证后台执行顺序
代码看完之后,我觉得这类异步流程最好还是靠日志再验证一遍。因为异步任务不像普通接口那样从 Controller 一路断点调下去就能完整看到返回结果,它更像是"请求线程负责投递任务,工作线程负责慢慢执行"。
于是我给几个关键节点补了一版日志:
收到新增场景请求;
场景主表创建完成;
后台任务提交成功;
后台任务开始执行;
每一类明细数据开始初始化;
每一类明细数据初始化完成;
完成状态校验开始;
最终状态更新完成;
异常时记录失败原因并标记创建失败。
补完以后,我重新调接口观察控制台输出。这样能很清楚地看到完整调用链路:接口先返回,后台线程继续执行,每个初始化步骤按顺序打印日志,最后再根据校验结果刷新状态。
从日志上看,主流程和预期是一致的。创建请求不会长时间阻塞,后台任务也能正常进入各个初始化步骤。这个过程虽然只是补日志和验证,但对这种异步任务来说挺重要的。因为只看接口返回成功,并不能证明后台真的完整生成了数据。
三、完成状态校验的处理逻辑
这次还有一个重点是场景完成状态校验。
以前场景状态只有"已完成"和"未完成",逻辑相对粗一些。现在加了"创建中"和"创建失败"以后,就不能再简单地把所有状态都当成业务完成状态处理。
我这边把状态拆成两类来看:
第一类是创建过程状态,比如"创建中"和"创建失败"。它们表示后台任务本身的执行情况。
第二类是业务完整性状态,比如"已完成"和"未完成"。它们表示场景数据是否满足后续计算或载入要求。
所以后台生成完成之后,需要再跑一遍完整性校验。校验内容大概包括:
场景基础字段是否完整,比如开始时间、结束时间、典型时间类型等;
系统负荷预测是否覆盖场景日期范围;
正备用、负备用约束是否逐日齐全;
机组最大出力、最小出力约束是否齐全;
碳价数据是否能覆盖场景周期;
关联申报方案中的报价状态是否满足要求。
这里没有把所有判断写成一个很长的 if,而是按数据模块拆开。比如负荷预测有单独的校验逻辑,备用约束有单独的校验逻辑,机组约束和碳价也分别处理。这样后续如果某个模块的完成口径变了,就可以直接定位到对应方法。
同时,状态刷新也不能只发生在创建结束时。因为场景创建完成之后,用户还可能继续修改预测数据、重新生成约束、更新碳价或调整申报数据。这些操作都会影响场景是否完成,所以数据变化后也需要重新计算场景状态。
不过这里也要避免一个问题:普通数据修改不能覆盖"创建中"和"创建失败"。比如后台任务还没结束,其他流程触发了一次状态刷新,如果直接把场景刷成"已完成"或"未完成",就会打乱创建流程。因此刷新状态时需要跳过这两类创建过程状态。
四、提交前审查发现的第一个边界问题
下午准备提交代码前,我感觉这次改动范围还是比较大,如果只靠自己肉眼看可能会漏掉边界场景。于是我让 AI 辅助审查了一遍这次改动。
结果确实发现了一个问题:完成状态校验和数据来源之间存在边界冲突。
完整性校验里会检查机组约束数据是否齐全,但不是所有数据来源都会生成完整的机组约束数据。如果某些来源下本来就不会生成这部分数据,校验逻辑却仍然强制要求它存在,就会导致场景一直被判定为"未完成"。
这个问题比较隐蔽,因为主流程测试时不一定会覆盖所有数据来源。正常创建可能没问题,但换一种数据来源之后,后台数据生成逻辑和完成状态校验逻辑就对不上了。
我先把这个点和组长确认了一下。组长说,如果数据来源是"算法包 + 运行数据",后续应该会初始化基础约束数据,只是当前这一块逻辑还没有完全补齐,所以这个分支暂时不用改。
最后我只对纯运行数据的部分场景加了兜底处理:如果当前数据来源和典型时间类型不适合继续走完整明细校验,就直接判定为未完成,不再进入后续依赖机组约束数据的校验流程。
这个改动本身不大,但它能避免校验逻辑误伤特殊来源的数据。因为"没有生成这类数据"和"数据生成了但不完整"其实是两种不同情况,不能完全用同一套规则硬套。
五、提交前审查发现的第二个边界问题
第二个问题和正负备用约束重新生成有关。
场景完成状态里会校验正备用、负备用约束是否齐全,所以当用户重新生成正负备用约束后,场景状态也应该重新计算。
原来的刷新逻辑依赖一个数据列表:先通过前面生成出来的数据列表反推出场景 ID,再根据这些场景 ID 去刷新状态。这个思路在大多数情况下是可以的,因为只要生成出了数据,列表里就能拿到 caseId。
但问题在于,这个列表有可能为空。
比如某些情况下,原来的正负备用数据已经被删除了,但新的数据没有生成出来,或者因为前置数据不足导致计算结果为空。此时业务数据其实已经发生变化,场景完整性也应该重新判断。可是如果刷新逻辑依赖"生成后的列表",列表为空时就拿不到场景 ID,后续状态刷新也不会触发。
这样就会出现一个不太容易发现的问题:正负备用表的数据已经变了,但场景状态还是旧的。前端看到的状态可能仍然是"已完成",但实际上约束数据已经不满足完整性要求。
修复这个问题时,我没有继续依赖数据列表反推场景 ID,而是直接使用当前操作本身已经知道的场景 ID 去刷新状态。这样即使本次重新生成的约束数据为空,也能触发完整性校验。
这个地方比较典型:有些业务操作的结果为空,并不代表什么都没发生。尤其是"先删除再生成"这种逻辑,只要删除动作已经执行,状态就可能已经需要重新计算。
六、再次验证状态刷新效果
修完两个问题之后,我又重新跑了一遍接口。
这次主要观察几个点:
新建场景后,主记录是否先进入"创建中";
后台任务是否能按顺序生成明细数据;
正常生成结束后,状态是否能刷新成"已完成"或"未完成";
异常情况下是否能进入"创建失败";
运行数据来源下是否会避开不适合的完整性校验;
正负备用重新生成后,即使生成列表为空,也能触发场景状态刷新。
从日志和接口返回结果看,这几个点都能按预期执行。尤其是正负备用重新生成那里,改成按场景 ID 刷新之后,逻辑会更稳定一些,不会被空列表卡住。
七、最终整理提交
最后我把今天的修改整理了一下再提交。整体上,今天不是在写一个全新的大功能,而是在昨天主体代码的基础上做加固。
主要处理了三类事情:
给异步创建流程补充日志,方便观察后台任务执行链路;
调整完成状态校验的边界逻辑,避免特殊数据来源下误判;
修复正负备用重新生成后状态刷新不触发的问题。
这次改动之后,场景创建流程会比之前更清晰:前端不用一直等待接口完成,后端通过后台线程生成数据,再通过状态字段告诉用户当前场景处于创建中、创建失败、已完成还是未完成。
到下班前,代码已经提交完成。今天主要时间都花在检查细节和补边界场景上,虽然没有像昨天那样写很多新逻辑,但这些问题如果不提前处理,后面联调或者测试时很可能会变成比较难排查的状态不一致问题。