Merge branch 'dev-bms-260826' into test
This commit is contained in:
+102
@@ -2,12 +2,14 @@ package com.mhd.bms.application.server.receiptManage;
|
||||
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.mhd.bms.domain.billManage.entity.BillManage;
|
||||
import com.mhd.bms.domain.billManage.service.BillManageDomainService;
|
||||
import com.mhd.bms.domain.receiptManage.entity.ReceiptManage;
|
||||
import com.mhd.bms.domain.receiptManage.repository.po.ReceiptManagePO;
|
||||
import com.mhd.bms.domain.receiptManage.repository.todo.ReceiptManageDO;
|
||||
import com.mhd.bms.domain.receiptManage.service.ReceiptManageDomainService;
|
||||
import com.mhd.bms.infrastructure.utils.CheckPasswordUtil;
|
||||
import com.mhd.bms.interfaces.dto.receiptManage.ReceiptDetailDTO;
|
||||
import com.mhd.bms.interfaces.dto.receiptManage.ReceiptManageDTO;
|
||||
import com.mhd.common.core.exception.ServiceException;
|
||||
import com.mhd.common.core.exception.digitalLogisticsException.DigitalLogisticsException;
|
||||
@@ -18,10 +20,13 @@ import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.transaction.interceptor.TransactionAspectSupport;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
@@ -147,6 +152,103 @@ public class ReceiptManageApplicationService {
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 排队批量收款
|
||||
* 接收集合按顺序逐笔处理,任意一笔收款失败则整体回滚
|
||||
*/
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public Boolean queueBatchCollection(List<ReceiptManageDO> receiptManageDOList) {
|
||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||||
if (ObjectUtil.isNull(loginUser)) {
|
||||
throw new DigitalLogisticsException(UserError.TIMEOUT);
|
||||
}
|
||||
int index = 1;
|
||||
for (ReceiptManageDO receiptManageDO : receiptManageDOList) {
|
||||
try {
|
||||
//根据账单id查询账单当前金额数据,替换前端传的账单金额、已收金额、未收金额等(仅保留前端传的收款金额和优惠金额)
|
||||
fillBillAmountFromBillManage(receiptManageDO);
|
||||
Boolean flag = batchCollection(receiptManageDO);
|
||||
if (!Boolean.TRUE.equals(flag)) {
|
||||
throw new ServiceException("收款数据处理失败");
|
||||
}
|
||||
} catch (Exception e) {
|
||||
//自己try捕获的异常也要标记事务回滚,确保收款失败时全部回滚
|
||||
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
|
||||
throw new ServiceException("第" + index + "笔收款失败:" + e.getMessage() + ",本次收款已全部回滚");
|
||||
}
|
||||
index++;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 排队批量收款-根据账单id查询账单当前金额,替换前端传的账单金额、已收金额、未收金额、剩余未收(仅保留前端传的收款金额和优惠金额)
|
||||
*/
|
||||
private void fillBillAmountFromBillManage(ReceiptManageDO receiptManageDO) {
|
||||
List<ReceiptDetailDTO> receiptDetailDTOList = receiptManageDO.getReceiptDetailDTOList();
|
||||
if (null == receiptDetailDTOList || receiptDetailDTOList.size() == 0) {
|
||||
throw new ServiceException("费用结算明细不能为空");
|
||||
}
|
||||
//汇总收款明细的账单id并查询账单当前数据
|
||||
Set<Long> billManageIdSet = receiptDetailDTOList.stream().map(ReceiptDetailDTO::getBillManageId).filter(ObjectUtil::isNotNull).collect(Collectors.toSet());
|
||||
if (billManageIdSet.size() == 0) {
|
||||
throw new ServiceException("收款账单不能为空");
|
||||
}
|
||||
LambdaQueryWrapper<BillManage> queryWrapper = new LambdaQueryWrapper<>();
|
||||
queryWrapper.in(BillManage::getBillManageId, billManageIdSet);
|
||||
queryWrapper.eq(BillManage::getDelFlag, 1);
|
||||
List<BillManage> billManageList = billManageDomainService.queryListLambda(queryWrapper);
|
||||
if (null == billManageList || billManageList.size() == 0) {
|
||||
throw new ServiceException("账单信息未找到");
|
||||
}
|
||||
Map<Long, BillManage> billManageMap = billManageList.stream().collect(Collectors.toMap(BillManage::getBillManageId, billManage -> billManage, (oldValue, newValue) -> oldValue));
|
||||
//汇总账单金额、已收金额、未收金额、收款金额、优惠金额
|
||||
BigDecimal billAmount = BigDecimal.ZERO;
|
||||
BigDecimal billAmountSettlement = BigDecimal.ZERO;
|
||||
BigDecimal billAmountUnsettled = BigDecimal.ZERO;
|
||||
BigDecimal collectedAmount = BigDecimal.ZERO;
|
||||
BigDecimal collectedDiscount = BigDecimal.ZERO;
|
||||
for (ReceiptDetailDTO receiptDetailDTO : receiptDetailDTOList) {
|
||||
BillManage billManage = billManageMap.get(receiptDetailDTO.getBillManageId());
|
||||
if (null == billManage) {
|
||||
throw new ServiceException("账单" + receiptDetailDTO.getBillNumber() + "信息未找到,请刷新后重试");
|
||||
}
|
||||
//只取前端传的收款金额、优惠金额,为空按0处理
|
||||
BigDecimal detailCollectedAmount = null == receiptDetailDTO.getCollectedAmount() ? BigDecimal.ZERO : receiptDetailDTO.getCollectedAmount();
|
||||
BigDecimal detailCollectedDiscount = null == receiptDetailDTO.getCollectedDiscount() ? BigDecimal.ZERO : receiptDetailDTO.getCollectedDiscount();
|
||||
//账单当前金额以数据库为准
|
||||
BigDecimal detailBillAmount = billManage.getBillAmount();
|
||||
BigDecimal detailBillAmountSettlement = billManage.getBillAmountSettlement();
|
||||
BigDecimal detailBillAmountUnsettled = billManage.getBillAmountUnsettled();
|
||||
//剩余未收金额 = 未收金额 - 收款金额 - 优惠金额
|
||||
BigDecimal detailBillAmountUnsettledResidue = detailBillAmountUnsettled.subtract(detailCollectedAmount).subtract(detailCollectedDiscount);
|
||||
if (detailBillAmountUnsettledResidue.compareTo(BigDecimal.ZERO) < 0) {
|
||||
throw new ServiceException("账单" + billManage.getBillNumber() + "未收金额小于本次收款金额与优惠金额之和,无法完成收款,请刷新后重试");
|
||||
}
|
||||
//用数据库当前值替换前端传的账单金额、已收金额、未收金额、剩余未收
|
||||
receiptDetailDTO.setBillAmount(detailBillAmount);
|
||||
receiptDetailDTO.setBillAmountSettlement(detailBillAmountSettlement);
|
||||
receiptDetailDTO.setBillAmountUnsettled(detailBillAmountUnsettled);
|
||||
receiptDetailDTO.setBillAmountUnsettledResidue(detailBillAmountUnsettledResidue);
|
||||
//累计收款单金额
|
||||
billAmount = billAmount.add(detailBillAmount);
|
||||
billAmountSettlement = billAmountSettlement.add(detailBillAmountSettlement);
|
||||
billAmountUnsettled = billAmountUnsettled.add(detailBillAmountUnsettled);
|
||||
collectedAmount = collectedAmount.add(detailCollectedAmount);
|
||||
collectedDiscount = collectedDiscount.add(detailCollectedDiscount);
|
||||
}
|
||||
//收款单金额:账单金额、已收金额、未收金额取查询值汇总,收款金额、优惠金额取前端传值汇总
|
||||
receiptManageDO.setBillAmount(billAmount);
|
||||
receiptManageDO.setBillAmountSettlement(billAmountSettlement);
|
||||
receiptManageDO.setBillAmountUnsettled(billAmountUnsettled);
|
||||
receiptManageDO.setCollectedAmount(collectedAmount);
|
||||
receiptManageDO.setCollectedDiscount(collectedDiscount);
|
||||
//剩余未收 = 未收金额 - 收款金额 - 优惠金额
|
||||
receiptManageDO.setUncollectedAmount(billAmountUnsettled.subtract(collectedAmount).subtract(collectedDiscount));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 撤销收款
|
||||
*/
|
||||
|
||||
@@ -4,6 +4,7 @@ import java.util.ArrayList;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.TreeSet;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
@@ -56,6 +57,11 @@ public class ReceiptManageApi extends BaseController{
|
||||
@Autowired
|
||||
private RedisLock redisLock;
|
||||
|
||||
/**
|
||||
* 排队批量收款-获取账单锁的最大等待时间(秒),等待期内排队获取锁,避免并发收款直接失败
|
||||
*/
|
||||
private static final long QUEUE_COLLECTION_LOCK_WAIT_SECONDS = 30L;
|
||||
|
||||
/**
|
||||
* 分页查询收款单管理列表
|
||||
*/
|
||||
@@ -115,6 +121,65 @@ public class ReceiptManageApi extends BaseController{
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 排队批量收款
|
||||
* 接收多个批量收款请求参数集合,整体加锁后按顺序排队依次处理;
|
||||
* 同一账单分多次收款时前一笔处理完成后才处理下一笔,避免redis锁冲突导致收款失败;
|
||||
* 整个集合在同一事务内处理,任意一笔收款失败则全部回滚
|
||||
*/
|
||||
@ApiOperation("排队批量收款")
|
||||
@RepeatSubmit
|
||||
@PostMapping("/queueBatchCollection")
|
||||
public AjaxResult queueBatchCollection(@RequestBody List<ReceiptManageDTO> receiptManageDTOList)
|
||||
{
|
||||
if(StringUtils.isEmpty(receiptManageDTOList)){
|
||||
throw new ServiceException("收款账单不能为空");
|
||||
}
|
||||
//汇总所有收款请求涉及的账单id并排序,整体加锁,保证事务期间锁不释放且加锁顺序一致避免死锁
|
||||
Set<String> lockKeySet = new TreeSet<>();
|
||||
for (ReceiptManageDTO receiptManageDTO : receiptManageDTOList) {
|
||||
if(!StringUtils.isNotEmpty(receiptManageDTO.getBillManageIds())){
|
||||
throw new ServiceException("收款账单不能为空");
|
||||
}
|
||||
lockKeySet.addAll(Stream.of(receiptManageDTO.getBillManageIds().split(",")).filter(StringUtils::isNotEmpty).map(String::trim).collect(Collectors.toSet()));
|
||||
}
|
||||
RedisLockTypeEnum redisLockTypeEnum = RedisLockTypeEnum.BMS;
|
||||
List<RLock> locks = new ArrayList<>();
|
||||
lockKeySet.forEach(x -> {
|
||||
//获取唯一key值
|
||||
String key = redisLockTypeEnum.getUniqueKey(UniqueKeyUtil.getBillingKey(x));
|
||||
locks.add(redisLock.getRLock(key));
|
||||
});
|
||||
RedissonMultiLock redissonMultiLock = new RedissonMultiLock(locks.toArray(new RLock[lockKeySet.size()]));
|
||||
boolean payInfoDetailLock = false;
|
||||
try {
|
||||
//尝试获取锁 等待指定时间内排队获取 持有锁10分钟
|
||||
payInfoDetailLock = redissonMultiLock.tryLock(QUEUE_COLLECTION_LOCK_WAIT_SECONDS, 10, TimeUnit.MINUTES);
|
||||
if (!payInfoDetailLock) {
|
||||
redissonMultiLock = null;
|
||||
return AjaxResult.error("账单正在处理,请稍后再试");
|
||||
}
|
||||
log.info("排队批量收款-生成账单加锁成功");
|
||||
//转换实体
|
||||
List<ReceiptManageDO> receiptManageDOList = new ArrayList<>();
|
||||
for (ReceiptManageDTO receiptManageDTO : receiptManageDTOList) {
|
||||
ReceiptManageDO receiptManageDO = new ReceiptManageDO();
|
||||
BeanUtils.copyProperties(receiptManageDTO,receiptManageDO);
|
||||
receiptManageDOList.add(receiptManageDO);
|
||||
}
|
||||
//事务内排队逐笔收款,任意一笔失败全部回滚
|
||||
return toAjax(receiptManageApplicationService.queueBatchCollection(receiptManageDOList));
|
||||
}catch (Exception e){
|
||||
//异常已在应用服务层事务内标记回滚(收款失败必回滚),此处仅返回失败结果
|
||||
return AjaxResult.error("收款失败:" + e.getMessage());
|
||||
}finally {
|
||||
if (redissonMultiLock != null && payInfoDetailLock) {
|
||||
redissonMultiLock.unlock();
|
||||
log.info("排队批量收款-生成账单释放了锁");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 撤销收款
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user