365 lines
20 KiB
Java
365 lines
20 KiB
Java
package com.ruoyi.system.task;
|
||
|
||
import cn.hutool.core.util.ObjectUtil;
|
||
import com.alibaba.fastjson2.JSON;
|
||
import com.ruoyi.common.constant.CacheConstants;
|
||
import com.ruoyi.common.constant.Constants;
|
||
import com.ruoyi.common.constant.ServiceConstants;
|
||
import com.ruoyi.common.core.domain.entity.AsUser;
|
||
import com.ruoyi.common.core.domain.entity.SysUser;
|
||
import com.ruoyi.common.core.redis.RedisCache;
|
||
import com.ruoyi.common.exception.ServiceException;
|
||
import com.ruoyi.common.utils.DateUtils;
|
||
import com.ruoyi.common.utils.spring.SpringUtils;
|
||
import com.ruoyi.system.domain.*;
|
||
import com.ruoyi.system.mapper.AsDeviceMapper;
|
||
import com.ruoyi.system.mapper.AsUserMapper;
|
||
import com.ruoyi.system.mapper.EtOrderMapper;
|
||
import com.ruoyi.system.mapper.SysUserMapper;
|
||
import com.ruoyi.system.service.*;
|
||
import com.wechat.pay.java.service.payments.model.Transaction;
|
||
import com.wechat.pay.java.service.refund.model.Refund;
|
||
import lombok.extern.slf4j.Slf4j;
|
||
import org.springframework.beans.factory.annotation.Autowired;
|
||
import org.springframework.stereotype.Component;
|
||
import org.springframework.transaction.annotation.Transactional;
|
||
|
||
import javax.annotation.PostConstruct;
|
||
import javax.annotation.Resource;
|
||
import java.math.BigDecimal;
|
||
import java.time.LocalDate;
|
||
import java.time.format.DateTimeFormatter;
|
||
import java.util.*;
|
||
import java.util.concurrent.ScheduledExecutorService;
|
||
import java.util.concurrent.TimeUnit;
|
||
import java.util.stream.IntStream;
|
||
|
||
/**
|
||
* 定时任务调度测试
|
||
*
|
||
* @author ruoyi
|
||
*/
|
||
@Slf4j
|
||
@Component("etTask")
|
||
public class EtTask {
|
||
|
||
@Resource
|
||
private EtOrderMapper etOrderMapper;
|
||
|
||
@Autowired
|
||
private IEtOperatingAreaService etOperatingAreaService;
|
||
|
||
@Resource
|
||
private SysUserMapper userMapper;
|
||
|
||
@Autowired
|
||
private IEtDividendDetailService dividendDetailService;
|
||
|
||
@Autowired
|
||
private ScheduledExecutorService scheduledExecutorService;
|
||
|
||
@Autowired
|
||
private IEtOrderService etOrderService;
|
||
|
||
@Resource
|
||
private AsDeviceMapper asDeviceMapper;
|
||
|
||
@Autowired
|
||
private IWxPayService wxPayService;
|
||
|
||
@Autowired
|
||
private IEtFeeRuleService etFeeRuleService;
|
||
|
||
@Autowired
|
||
private IEtRefundService etRefundService;
|
||
|
||
@Autowired
|
||
private RedisCache redisCache;
|
||
|
||
@Resource
|
||
private AsUserMapper asUserMapper;
|
||
|
||
@Autowired
|
||
private CallbackService callbackService;
|
||
|
||
|
||
|
||
/**
|
||
* 1.启动时判断是否有未取消预约的订单
|
||
* 2.判断已完成的订单未退还押金的
|
||
* 3.启动时判断是否分账
|
||
*/
|
||
@Transactional
|
||
@PostConstruct
|
||
public void init() {
|
||
log.info("=========================启动业务处理=========================");
|
||
log.info("=========================开始=========================");
|
||
/** 1.启动时判断是否有未取消预约的订单*/
|
||
uncancelledAppointmentHandle();
|
||
|
||
/** 2.判断已完成的订单未退还押金的(根据et_refund表中的refund_result结果判断是否已经退款) */
|
||
/** ①找出所有已完成的订单 status=4 type = 1 r.refund_result IS NULL
|
||
* ②根据用户查询最后一次押金充值记录
|
||
*/
|
||
List<EtOrder> orders = etOrderMapper.selectUserListFinishOrder();
|
||
for(EtOrder order:orders){
|
||
EtFeeRule rule = etFeeRuleService.selectEtFeeRuleByRuleId(order.getRuleId());
|
||
if(ObjectUtil.isNull(rule)){
|
||
throw new ServiceException("骑行订单:【"+order.getOrderNo()+"】未找到该套餐【"+order.getRuleId()+"】");
|
||
}
|
||
EtOperatingArea area = etOperatingAreaService.selectEtOperatingAreaByAreaId(order.getAreaId());
|
||
AsUser asUser = asUserMapper.selectUserById(order.getUserId());
|
||
Integer autoRefundDeposit = rule.getAutoRefundDeposit();
|
||
|
||
// 根据用户查询最后一次押金充值订单
|
||
EtOrder etOrder = new EtOrder();
|
||
etOrder.setUserId(order.getUserId());
|
||
etOrder.setPaid(ServiceConstants.ORDER_PAY_STATUS_PAID);
|
||
etOrder.setType(ServiceConstants.ORDER_TYPE_DEPOSIT);
|
||
etOrder.setStatus(ServiceConstants.ORDER_STATUS_ORDER_END);
|
||
List<EtOrder> etOrders = etOrderMapper.selectEtOrderList(etOrder);
|
||
if (etOrders.size() > 0 || ObjectUtil.isNotNull(etOrders) ) {
|
||
Optional<EtOrder> latestOrderOptional = etOrders.stream()
|
||
.max(Comparator.comparing(EtOrder::getPayTime));
|
||
if (latestOrderOptional.isPresent()) {
|
||
EtOrder lastOrder = latestOrderOptional.get();
|
||
log.info("【系统启动】用户【{}】最后一次押金充值订单 : " + JSON.toJSONString(lastOrder),asUser.getUserId());
|
||
if(lastOrder.getTotalFee().compareTo(new BigDecimal(area.getDeposit()))!=0){
|
||
log.info("【系统启动】最后一次押金充值记录 金额与押金不一致,订单押金:【{}】,区域押金:【{}】",lastOrder.getTotalFee(),area.getDeposit());
|
||
}else{
|
||
// 根据最新的订单号,查询是否有退款记录
|
||
EtRefund etRefund = etRefundService.selectEtRefundByOrderNo(lastOrder.getOrderNo());
|
||
// 没有退款记录,发起退款
|
||
if(ObjectUtil.isNull(etRefund)){
|
||
// 根据订单支付时间 autoRefundDeposit个小时后退押金
|
||
String reason = autoRefundDeposit + "个小时后自动退押金";
|
||
Date payTime = order.getPayTime();
|
||
Date refundDepositTime = DateUtils.getTimeAfterXHours(payTime, autoRefundDeposit);
|
||
Date nowDate = DateUtils.getNowDate();
|
||
if (nowDate.after(refundDepositTime)) {
|
||
log.info("【系统启动】用户【{}】押金充值订单【{}】已过期,开始自动退押金",asUser.getUserId(),lastOrder.getOrderNo());
|
||
refundDeposit(asUser, lastOrder, reason);
|
||
}else{
|
||
int timeDifferenceInMinutes = DateUtils.timeDifferenceInMinutes(payTime, nowDate);
|
||
int i = autoRefundDeposit * 60;
|
||
int delay = i - timeDifferenceInMinutes;
|
||
log.info("【系统启动】用户【{}】押金充值订单【{}】未过期,【{}】分钟后退押金",asUser.getUserId(),lastOrder.getOrderNo(),delay);
|
||
scheduledExecutorService.schedule(() -> {
|
||
refundDeposit(asUser, lastOrder, reason);
|
||
}, delay, TimeUnit.MINUTES);
|
||
}
|
||
}else{
|
||
// 有退款记录,判断是否成功
|
||
if(!Constants.SUCCESS2.equals(etRefund.getRefundResult())){
|
||
log.info("【系统启动】押金退款未成功回调,退款单号:【{}】",etRefund.getRefundNo());
|
||
// 根据退款单号查询退款信息
|
||
Refund refund = wxPayService.queryByOutRefundNo(area.getAreaId(),etRefund.getRefundNo());
|
||
if(ObjectUtil.isNotNull(refund) && Constants.SUCCESS2.equals(refund.getStatus().name())){
|
||
// 更新退款记录
|
||
etRefund.setRefundResult(Constants.SUCCESS2);
|
||
etRefund.setUpdateTime(new Date());
|
||
etRefundService.updateEtRefund(etRefund);
|
||
log.info("【系统启动】更新押金退款回调成功,退款单号:【{}】",refund.getOutRefundNo());
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
/** 3.启动时判断是否分账(根据订单号查询分账明细表是否有记录来判断是否分账) */
|
||
/** ①找出所有已完成的骑行订单 status=4 type = 1 r.refund_result IS NULL
|
||
* ②根据订单号查询分账明细表是否有记录
|
||
* 有记录则已经分账过
|
||
* 没值代表还未分账
|
||
* 判断是否已过分账时间
|
||
* 未过,计算出多少小时后分账
|
||
* 已过,直接分账(记录分账明细表)
|
||
*/
|
||
// 查询所有待分账的订单
|
||
List<EtOrder> needDividendOrders = etOrderMapper.selectNeedDividendOrder();
|
||
for(EtOrder order: needDividendOrders){
|
||
log.info("【系统启动】待分账订单:【{}】",order.getOrderNo());
|
||
EtOperatingArea area = etOperatingAreaService.selectEtOperatingAreaByAreaId(order.getAreaId());
|
||
if(dividendDetailService.isDividendComputedByOrderNo(order.getOrderNo())){
|
||
log.info("订单【{}】已经分账",order.getOrderNo());
|
||
break;
|
||
}
|
||
log.info("【系统启动】骑行订单【{}】未分账,开始分账",order.getOrderNo());
|
||
Date payTime = order.getPayTime();
|
||
Date dividendTime = DateUtils.getTimeAfterXHours(payTime, 24);//分账时间
|
||
Date nowDate = DateUtils.getNowDate();
|
||
if (nowDate.after(dividendTime)) {
|
||
log.info("【系统启动】骑行订单【{}】已过分账时间,开始分账",order.getOrderNo());
|
||
// 请求分账处理
|
||
Transaction transaction = wxPayService.queryOrderByOutTradeNo(order.getOrderNo());
|
||
if (callbackService.dividendHandle(transaction.getTransactionId(), order, area)) break;
|
||
}else{
|
||
int timeDifferenceInHours = DateUtils.timeDifferenceInHours(payTime, nowDate);
|
||
int delay = 24 - timeDifferenceInHours;
|
||
log.info("【系统启动】骑行订单【{}】未过分账时间,【{}】小时后开始分账",order.getOrderNo(),delay);
|
||
// 24小时后发起分账
|
||
scheduledExecutorService.schedule(() -> {
|
||
// 请求分账处理
|
||
Transaction transaction = wxPayService.queryOrderByOutTradeNo(order.getOrderNo());
|
||
if (callbackService.dividendHandle(transaction.getTransactionId(), order, area)) return;
|
||
}, delay , TimeUnit.HOURS);
|
||
}
|
||
}
|
||
log.info("=========================结束=========================");
|
||
}
|
||
|
||
|
||
private void refundDeposit(AsUser asUser, EtOrder lastOrder, String reason) {
|
||
Refund refund = wxPayService.refund(lastOrder, reason, lastOrder.getTotalFee());
|
||
lastOrder.setReason(reason);
|
||
EtRefund refund1= etOrderService.createRefund(lastOrder, lastOrder.getTotalFee(), null, null, null, null, refund,ServiceConstants.REFUND_TYPE_DEPOSIT);
|
||
if(etRefundService.insertEtRefund(refund1)>0){
|
||
log.info("【自动退款】保存退款对象成功");
|
||
// 新增资金流水记录
|
||
callbackService.capitalFlowRecords(lastOrder,ServiceConstants.FLOW_TYPE_DISBURSE,ServiceConstants.ORDER_TYPE_DEPOSIT_REFUND);
|
||
// 更新用户信息,清除缓存
|
||
asUser.setBalance(BigDecimal.ZERO);
|
||
int updateUser = asUserMapper.updateUser(asUser);
|
||
if(updateUser>0){
|
||
Collection<String> keys = SpringUtils.getBean(RedisCache.class).keys(CacheConstants.LOGIN_TOKEN_KEY + "*");
|
||
redisCache.deleteObject(keys);
|
||
log.info("【系统启动】退还押金,更新用户余额成功!");
|
||
}
|
||
log.info("=================【系统启动】退还押金定时任务结束!!!==================");
|
||
}else{
|
||
throw new ServiceException("【系统启动】保存退款对象失败");
|
||
}
|
||
}
|
||
|
||
private void uncancelledAppointmentHandle() {
|
||
List<EtOrder> orders= etOrderMapper.selectAppointmentUnfinished();
|
||
log.info("预约未完成的订单 = " + JSON.toJSONString(orders));
|
||
for (EtOrder order:orders) {
|
||
EtOperatingArea area = etOperatingAreaService.selectEtOperatingAreaByAreaId(order.getAreaId());
|
||
AsDevice asDevice = asDeviceMapper.selectAsDeviceBySn(order.getSn());
|
||
Date appointmentEndTime = DateUtils.getTimeAfterXMinutes(order.getAppointmentStartTime(), area.getTimeoutMinutes());//预约结束时间
|
||
int timeDifferenceInSeconds = DateUtils.timeDifferenceInSeconds(appointmentEndTime, order.getAppointmentStartTime());//(超时时间-开始时间)的秒数
|
||
int differenceInSeconds = DateUtils.timeDifferenceInSeconds(new Date(), order.getAppointmentStartTime());//(当前时间-开始时间)的秒数
|
||
int delay = timeDifferenceInSeconds - differenceInSeconds;
|
||
log.info("【定时取消预约】延迟:【{}】秒", delay);
|
||
//定时取消预约
|
||
scheduledExecutorService.schedule(() -> {
|
||
log.error("【车辆超时预约】系统自动取消");
|
||
EtOrder order1 = etOrderService.selectEtOrderByOrderNo(order.getOrderNo());
|
||
log.info("【定时取消预约】重新获取订单信息:{}",JSON.toJSON(order1));
|
||
if(order1.getPaid().equals(ServiceConstants.ORDER_PAY_STATUS_PAID)){//已支付订单,跳过
|
||
log.error("【车辆超时预约】订单已支付,跳过");
|
||
return;
|
||
}
|
||
log.error("【车辆超时预约】订单未支付,系统自动处理");
|
||
//未支付 订单更新最后预约时间,并结束订单,做超出预约时间标记
|
||
order.setStatus(ServiceConstants.ORDER_STATUS_CANCEL_APPOINTMENT);
|
||
order.setAppointmentEndTime(new Date());
|
||
order.setAppointmentTimeout("1");
|
||
//计算预约费
|
||
BigDecimal appointmentServiceFee = area.getAppointmentServiceFee();
|
||
BigDecimal fee = appointmentServiceFee.multiply(new BigDecimal(area.getTimeoutMinutes()).divide(new BigDecimal(10)));
|
||
order.setAppointmentFee(fee);
|
||
order.setTotalFee(fee);
|
||
int update = etOrderService.updateEtOrder(order);
|
||
if(update==0){
|
||
throw new ServiceException("【车辆超时预约】:更新订单状态失败");
|
||
}
|
||
// 改变车辆状态
|
||
asDevice.setStatus(ServiceConstants.VEHICLE_STATUS_NORMAL);
|
||
asDevice.setLockStatus(ServiceConstants.LOCK_STATUS_OPEN);
|
||
int device = asDeviceMapper.updateAsDevice(asDevice);
|
||
if(device==0){
|
||
log.error("【车辆超时预约】更新车辆状态失败");
|
||
throw new ServiceException("【车辆超时预约】更新车辆状态失败");
|
||
}
|
||
}, delay, TimeUnit.SECONDS);
|
||
}
|
||
}
|
||
/**
|
||
* 每天凌晨0点5分执行,计算分账结果
|
||
* cron: 0 5 0 * * ?
|
||
*/
|
||
public void computeDividend()
|
||
{
|
||
log.info("每天凌晨0点5分执行,计算分账结果");
|
||
// 获取昨天的订单,2024-05-26 00:00:00 -- 2024-05-26 23:59:59
|
||
// 获取昨天日期格式: yyyy-MM-dd
|
||
|
||
LocalDate yesterday = LocalDate.now().minusDays(1);
|
||
DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd");
|
||
String formattedYesterday = yesterday.format(formatter);
|
||
log.info("获取昨天日期 = " + formattedYesterday);
|
||
|
||
//判断该日期是否已经计算过分账结果
|
||
if(dividendDetailService.isDividendComputed(LocalDate.now().format(formatter))){
|
||
log.info("该日期已经计算过分账结果");
|
||
return;
|
||
}
|
||
|
||
String startDateStr = formattedYesterday + " "+ Constants.DATE_FORMAT_START_PEREND;
|
||
String endDateStr = formattedYesterday + " " +Constants.DATE_FORMAT_END_PEREND;
|
||
|
||
EtOrder order = new EtOrder();
|
||
order.setStartTime(startDateStr);
|
||
order.setEndTime(endDateStr);
|
||
order.setPaid(ServiceConstants.ORDER_PAY_STATUS_PAID);
|
||
order.setStatus(ServiceConstants.ORDER_STATUS_ORDER_END);
|
||
order.setType(ServiceConstants.ORDER_TYPE_RIDING);
|
||
List<EtOrder> orderListByDate = etOrderMapper.selectEtOrderList(order);
|
||
for(EtOrder order1:orderListByDate){
|
||
EtDividendDetail etDividendDetail = new EtDividendDetail();
|
||
EtOperatingArea area = etOperatingAreaService.selectEtOperatingAreaByAreaId(order1.getAreaId());
|
||
|
||
SysUser sysUser = new SysUser();
|
||
sysUser.setUserType("03");
|
||
sysUser.setAreaId(area.getAreaId());
|
||
List<SysUser> sysUsers = userMapper.selectUserList(sysUser);
|
||
for(SysUser user : sysUsers){
|
||
etDividendDetail.setAreaId(area.getAreaId());
|
||
etDividendDetail.setPartnerId(user.getUserId());
|
||
etDividendDetail.setOrderNo(order1.getOrderNo());
|
||
etDividendDetail.setTotalAmount(order1.getTotalFee());
|
||
etDividendDetail.setCreateTime(DateUtils.getNowDate());
|
||
etDividendDetail.setDividendProportion(user.getDividendProportion());
|
||
String dividendItem = user.getDividendItem();
|
||
// todo 分账金额是骑行费,还是调度费,看分账项目 分账项目:1-骑行费(骑行费+预约费);2-调度费(调度费+管理费)
|
||
BigDecimal dividendAmount = BigDecimal.ZERO;
|
||
if(dividendItem.contains("1")){
|
||
dividendAmount.add(order1.getRidingFee().add(order1.getAppointmentFee()));//1-骑行费(骑行费+预约费)
|
||
}else if(dividendItem.contains("2")){
|
||
dividendAmount.add(order1.getManageFee().add(order1.getManageFee()));//2-调度费(调度费+管理费)
|
||
}
|
||
BigDecimal divide = new BigDecimal(user.getDividendProportion()).divide(new BigDecimal(100), 2, BigDecimal.ROUND_HALF_UP);
|
||
etDividendDetail.setDividendAmount(dividendAmount.multiply(divide));
|
||
etDividendDetail.setDividendItem(dividendItem);
|
||
log.info("保存分账明细 === " + JSON.toJSONString(etDividendDetail));
|
||
int i = dividendDetailService.insertEtDividendDetail(etDividendDetail);
|
||
if(i==0){
|
||
throw new ServiceException("保存分账明细失败");
|
||
}
|
||
}
|
||
int totalDividendProportion = IntStream.of(sysUsers.stream()
|
||
.mapToInt(SysUser::getDividendProportion)
|
||
.toArray())
|
||
.sum();
|
||
//算运营商自己的分账
|
||
etDividendDetail.setAreaId(area.getAreaId());
|
||
etDividendDetail.setPartnerId(0L);
|
||
etDividendDetail.setOrderNo(order1.getOrderNo());
|
||
etDividendDetail.setTotalAmount(order1.getTotalFee());
|
||
etDividendDetail.setCreateTime(DateUtils.getNowDate());
|
||
etDividendDetail.setDividendAmount(order1.getTotalFee().multiply(new BigDecimal(100-totalDividendProportion).divide(new BigDecimal(100),2, BigDecimal.ROUND_HALF_UP)));
|
||
etDividendDetail.setDividendProportion(100-totalDividendProportion);
|
||
etDividendDetail.setDividendItem("运营商");
|
||
int i = dividendDetailService.insertEtDividendDetail(etDividendDetail);
|
||
if(i==0){
|
||
throw new ServiceException("保存分账明细失败");
|
||
}
|
||
}
|
||
}
|
||
|
||
}
|