开发概述——退款记录导出至excel

最近做了一个(奇坑的)需求,用户退款信息导出至excel。听起来似乎是一个简单的小功能,但是做起来却发现有数不清的大坑- -,整个项目我大约花了2-3周时间去开发(当然中间穿插着一些其他的需求),并后续仍在优化或解决bug。不过现在已经完成,大部份的坑也已经被填满了。

首先来说下整体的开发流程图:

开发概述——退款记录导出至excel_第1张图片

对于这个需求,首先我们需要明确一点:必须要异步来做,导出任务很耗时间(当记录较多时,时间会比较长),如果采用同步策略,势必影响用户的体验。

然后我们来分析下开发的流程图,首先我们有两个异步的定时任务:正常的退款导出任务以及失败重试的定时任务。前者负责正常任务的执行,后者负责失败的任务的重试。那么怎么来定义任务?我们用了一张表:task表。建表语句如下:

开发概述——退款记录导出至excel_第2张图片

主要用到的表字段有以下几个:taskId, status, rule。taskId很好理解,每个任务都有自己的编号,我们需要根据编号来执行相依的任务。那么具体的执行呢?以退款导出为例,我们需要调用退款列表接口,需要的入参如下图展示的那样:

开发概述——退款记录导出至excel_第3张图片

可以看到有以下几个核心的参数:查询条件、查询的时间段(起始及终止时间)等。这些底层查询接口需要的查询参数就通过json序列化后写入rule字段中。

至于status状态字段,上面说了我们有两个异步任务。我们就根据status字段来判断任务的执行状态:

/**

 * 订单状态

 * 未完成 0, 已完成 1, 错误 2, 运行中 3

 */

INIT_EXPORT_ORDER_TASK_STATUS(0, "init"),

COMPLETED_EXPORT_ORDER_TASK_STATUS(1, "completed"),

ERROR_EXPORT_ORDER_TASK_STATUS(2, "error"),

RUNNING_EXPORT_ORDER_TASK_STATUS(3, "running");

有了上面的基本概述后,我们来描述下此功能整体的执行流程:

首先,当用户根据查询条件触发退款导出的接口时,前端会将查询参数传入后端,后端会在检查参数有效性、验证用户操作权限后,将入参json序列化后,向task表中插入此条记录。此时该任务的status0

同时,普通的定时任务会从task表中获取待执行(status为0)的所有任务,随后多线程异步处理这些任务。执行的步骤是获取任务中的rule字段,然后反序列化后得到入参后,随后调用退款信息接口,用户信息接口及订单信息接口。查询到所有的信息后将信息组装成excel(这里使用到了excel模板的功能,这里踩过一个神坑,详情请见上一篇博客)。组装成excel后并不是直接穿给用户,而是将excel上传至文件系统,并会生成一个下载链接,用户可以根据下载链接进行对应文件的下载。

这里曾经有一个巨坑,莫名出现退款导出任务经常失败的情况。debug后发现,是调用用户服务时出现异常,出现异常时导致整个退款导出任务返回失败。再与相关开发人员对齐后发现,我们调用的是老接口,而老接口已经被废弃,后来使用了用户服务提供的新接口进行重构,暂时恢复正常;但是当查询量过大时又出现任务出错的情况,后来发现是用户接口最多支持500数据的批量查询。

所以在这里需要分批处理任务。正常的循环调用接口会比较费时,后来我们使用了fork-join框架处理批量接口的调用(关于此框架,在下次我会总结下)。

从这个问题中我也是感触颇多,想给出大家我的血的教训:1.调用其他团队同学的接口一定要做好信息同步,别再出现老接口废弃了没人维护了,其他开发方还在使用的情况。2. 在代码功能实现上,不能因为某一个环节出错(用户信息查询失败)而导致整个功能出现问题(退款导出任务失败,导致用户没办法成功导出),因为用户信息并不是一个极其重要的信息,如果没查询到,大不了为空即可!优化功能的前提是主业务不能被影响。

为了避免因为某些神奇的异常情况导致正常的退款任务执行失败。特地加了一个失败重试的定时任务,专门执行status=3(执行中)的任务。可以看到上面的框图中有一个retryNum的计数器,限制了任务的重试次数。刚开始时是没有考虑这一点的,那么这样出现了一种什么情况呢?当拿到一个必然执行失败(比如由于逻辑判断,这个任务永远不会被成功执行)的任务后,此定时任务会一直执行此任务,又因为这个任务必然会执行失败,导致定时任务会阻塞。这就很尴尬了!这个任务执行不了,你还犟得和头牛一样一定要执行,这不浪费资源浪费时间么!所以我们设置了任务失败最多执行的次数,失败达到上限任务会被标定为真正的失败,即status=2

并且这种设计方式还有一个很尬的问题——因为当正常定时任务在正式执行任务前,会将任务的状态更新为3,而失败重试的任务将会执行状态为3的任务。这就会出现一种情况——任务也许会被执行多次,然而执行此类任务是一种幂等性的操作,执行多次并不影响结果;但是——总归不是一种优雅的设计方案了对吧?暂时也想不出更合理的执行方式~如果有大佬有思路,可以推荐哟~

这次的博客就到这里。


我希望世界杯决赛可以看到法国打英格兰,耶✌️

你可能感兴趣的:(Java)