今天组长给我安排了一个新的需求:把前面已经完成的几段流程串成一个完整的算法启动接口。
昨天我已经写好了两部分逻辑:一部分是根据业务数据生成算法需要的输入 Excel,另一部分是解析算法执行后生成的输出 Excel,并把结果写入数据库。今天要做的就是在它们外面再封装一个总入口,也就是:
public BaseResp<Void> startAlgo(String caseId)
这个接口的目标不是单纯"启动一下算法",而是把整个链路串起来:校验案例数据、生成输入 Excel、调用服务器上的算法程序、等待算法执行完成、解析结果 Excel、最后把结果入库。
实现的时候,我先在 Controller 层加了一个启动接口,接口接收 caseId,然后交给 Service 层处理。Service 里没有直接执行算法,而是先做了一些前置校验。比如 caseId 不能为空,数据库里必须能查到对应案例,并且案例里的历史开始年份、历史结束年份、预测结束年份都要完整。这里还加了年份范围判断,避免出现开始年份大于结束年份、历史结束年份大于预测结束年份这种明显不合法的数据。
真正调用算法前,我先调用了之前写好的总导出逻辑,重新生成输入 Excel。这里有一个细节:每次启动算法前都重新生成输入文件,而不是复用目录里已有的文件。这样可以避免算法读到上一次残留的旧 Excel,导致排查问题时分不清到底是哪次数据在生效。
输入 Excel 的生成没有直接新建空表,而是基于模板文件回填数据。代码里用 POI 读取模板,然后按固定 sheet 写入发电量数据、月度分配系数和预测参数。为了保持模板结构不被破坏,只覆盖约定的数据区域。比如城市顺序是从模板里提取出来的,发电量数据用"城市 + 年份"作为 key 去匹配数据库记录;月度分配系数按城市匹配;预测参数则优先使用当前值,没有当前值时再回退默认值。还有一些单位转换也放在导出阶段处理,比如发电量和百分比口径要换算成算法模板需要的格式。
算法调用部分使用了 ProcessBuilder。我没有拼接一整条命令字符串,而是把可执行文件路径、输入文件名、开始年份、结束年份、预测结束年份这些参数分开传进去。这样做的好处是路径里有中文、空格或者特殊字符时,不容易因为命令解析出错。算法程序、输入 Excel 和输出 Excel 都约定放在同一个算法目录下,启动进程时把工作目录设置到这个目录,算法就可以用相对路径读写文件。
算法执行时,我把标准输出和错误输出合并,然后读取算法日志。如果进程退出码不是 0,就认为算法执行失败,并把输出内容带到异常里,方便后续定位。只有算法正常结束后,才会继续解析结果 Excel。
结果解析这里也是按固定模板结构处理的。代码会先检查结果 Excel 是否存在,然后读取几个约定好的 sheet。导入数据库前,会先删除当前 caseId 已有的结果数据,再把本次解析出来的数据插入结果表。这样接口重复调用时不会产生重复数据。解析过程中还会做一些基础校验,比如 sheet 是否存在、月份是否在 1 到 12 之间、城市名称能不能匹配枚举等。
今天联调时也遇到了两个问题。因为算法程序只在服务器上,本地没办法完整验证,所以我把代码推到 GitLab 后,由组长合并到主分支并部署到测试服务器。第一次调测试服务器接口时,报了找不到模板 Excel 的问题。排查后发现配置里的路径实际是一个目录,而代码最开始按完整文件路径去读,所以找不到模板。后来我把模板路径解析逻辑调整成兼容两种情况:如果配置的是目录,就自动拼接固定模板名;如果配置的是具体文件,就直接读取该文件。同时加了文件存在和是否为普通文件的校验,这样错误信息会更清楚。
第二次测试时,又出现了 caseId 不合法相关的问题。这个接口牵扯的数据比较多,不只是案例主表,还包括边界数据、预测参数、结果表等。如果测试环境里某些表的数据不完整,接口就会在校验或生成模板时失败。最后我手动在数据库里构造了一条比较完整的测试数据,包括案例年份、发电量数据、月度系数和算法参数,再去调用测试服务器接口,整个流程终于跑通了。
今天最大的收获是:一个"启动接口"看起来只是一个按钮对应的后端接口,但真正实现时要考虑完整链路。尤其是这种涉及 Excel、外部算法程序和数据库结果入库的流程,不能只关注代码能不能执行,还要关注文件路径、模板结构、历史数据清理、异常信息、重复调用以及测试数据完整性。