在新一代票据系统中,有很多批量提交的功能,基本上所有的业务都需要支持批量提交,并且返回执行结果,由于之前项目中的代码没有设计好,所以普遍存在2个问题
- 事务问题
有的无事务,有的在批量方法上加事务,有的在单个业务方法加了事务,但是@Transactional失效 - 代码臃肿
执行结果的统计,处理的非常杂乱,返回值不统一,统计值不正确
优化后解决方案
1. 统一定义执行结果对象,无需定义参差不齐的众多类似的对象
/**
* 批量操作返回结果
*
* @author gaojie
* @date 2023-05-10
*/
@Data
public class BatchResult implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty("提交笔数")
private Integer submitNum = 0;
@ApiModelProperty("提交金额")
private BigDecimal submitAmount = BigDecimal.ZERO;
@ApiModelProperty("成功笔数")
private Integer successNum = 0;
@ApiModelProperty("成功金额")
private BigDecimal successAmount = BigDecimal.ZERO;
@ApiModelProperty("失败笔数")
private Integer failNum = 0;
@ApiModelProperty("失败金额")
private BigDecimal failAmount = BigDecimal.ZERO;
@ApiModelProperty("失败票据明细列表")
private List<BatchDetail> failList = new ArrayList<>();
/**
* 成功后统计
*
* @param amount 交易金额
*/
public void success(BigDecimal amount) {
submitNum++;
submitAmount = submitAmount.add(amount);
successNum++;
successAmount = successAmount.add(amount);
}
/**
* 失败后统计
*
* @param amount 交易金额
* @param draftNo 票据包号
* @param cdRange 子票区间
* @param msg 错误描述
*/
public void fail(BigDecimal amount, String draftNo, String cdRange, String msg) {
submitNum++;
submitAmount = submitAmount.add(amount);
failNum++;
failAmount = failAmount.add(amount);
failList.add(new BatchDetail(draftNo, cdRange, msg));
}
}
/**
* 批量操作明细
*
* @author gaojie
* @date 2023-05-10
*/
@Data
@AllArgsConstructor
public class BatchDetail implements Serializable {
private static final long serialVersionUID = 1L;
@ApiModelProperty("票据包号")
private String cdNo;
@ApiModelProperty("子票区间")
private String cdRange;
@ApiModelProperty("失败原因")
private String msg;
}
2. 定义批量执行并且自动收集执行结果的方法:简化代码,避免统计结果错误
/**
* 批量操作工具类
*
* @author gaojie
* @date 2023-05-11
*/
@Slf4j
public class BatchUtils {
/**
* 封装批量操作,统一返回执行结果,减少重复代码
*
* @param list 执行操作的主要对象集合
* @param cdNoFunc 获取票据号方法
* @param cdRangeFunc 获取子票区间方法
* @param amountFunc 获取金额方法
* @param action 执行方法逻辑
* @param <T> 集合对象类型
* @return 执行结果统计
*/
public static <T> BatchResult execute(List<T> list,
Function<T, String> cdNoFunc,
Function<T, String> cdRangeFunc,
Function<T, BigDecimal> amountFunc,
Consumer<T> action) {
log.info("批量操作开始");
BatchResult result = new BatchResult();
final StopWatch stopWatch = StopWatch.createStarted();
list.forEach(e -> {
BigDecimal amount = amountFunc.apply(e);
try {
action.accept(e);
result.success(amount);
} catch (Exception ex) {
log.warn("批量操作执行失败:{}", JSON.toJSONString(e), ex);
String cdNo = cdNoFunc.apply(e);
String cdRange = cdRangeFunc.apply(e);
result.fail(amount, cdNo, cdRange, ex.getMessage());
}
});
log.info("批量操作完成:[耗时={}ms],结果:{}", stopWatch.getTime(), JSON.toJSONString(result));
return result;
}
}
3. 使用示例
1. 方法逻辑不捕获异常场景
@Override
public BatchResult insert(Long custId, LoginUser currentUser, BBAdvancePaymentSaveDto bbAdvancePaymentSaveDto) {
CustomAssertUtil.notEmpty(bbAdvancePaymentSaveDto.getAdvancePaymentBillDtos(), "选票数据不能为空");
return BatchUtils.execute(
bbAdvancePaymentSaveDto.getAdvancePaymentBillDtos(),
BBAdvancePaymentBillDto::getDraftNo,
BBAdvancePaymentBillDto::getCdRange,
BBAdvancePaymentBillDto::getCdAmt,
e -> {
final String billNo = DIscountBusiSeqUtils.getBillNo();
bbAdvancePaymentService.saveEachRow(custId, billNo, currentUser, bbAdvancePaymentSaveDto, e);
}
);
}
2. 方法逻辑捕获异常场景
@Override
public BatchResult batchSubmit(List<Long> ids, LoginUser currentUser, Long currentCustId) {
if (CollUtil.isEmpty(ids)) {
throw HqException.notEmpty("出票申请id集合");
}
List<DraftTakeoffApplyDO> applyList = this.listByIds(ids);
if (CollUtil.isEmpty(applyList)) {
throw HqException.notEmpty("出票申请");
}
return BatchUtils.execute(
applyList,
DraftTakeoffApplyDO::getDraftNo,
DraftTakeoffApplyDO::getCdRange,
DraftTakeoffApplyDO::getDraftAmt,
e -> {
try {
// 提交出票申请
updateCheck(e);
submit(e, currentUser, currentCustId);
} catch (Exception ex) {
log.warn("提交出票申请失败: {}", ex.getMessage());
e.setResultRemark(StrUtil.sub(ex.getMessage(), 0, 200));
updateById(e);
throw ex;
}
}
);
}
网友评论