退款状态问题,全部退款,就取消订单

This commit is contained in:
Jack 2025-09-19 18:12:52 +08:00
parent 14261a1ad1
commit 63ff212c80
10 changed files with 240 additions and 38 deletions

View File

@ -52,7 +52,7 @@ public class GlobalExceptionHandler {
}
logError(req, "参数校验失败", e);
return CommonResult.validateFailed(errorMessage);
return CommonResult.validateFailed("数据是否有误,请检查!");
}
/**
@ -65,7 +65,7 @@ public class GlobalExceptionHandler {
.collect(Collectors.joining("; "));
logError(req, "约束违反异常", e);
return CommonResult.validateFailed(errorMessage);
return CommonResult.validateFailed("系统数据异常,请联系管理员!");
}
/**
@ -77,7 +77,7 @@ public class GlobalExceptionHandler {
e.getName(), e.getRequiredType().getSimpleName(), e.getValue());
logError(req, "参数类型不匹配", e);
return CommonResult.validateFailed(errorMessage);
return CommonResult.validateFailed("参数类型不匹配");
}
/**
@ -87,7 +87,7 @@ public class GlobalExceptionHandler {
public CommonResult handleParameterException(Exception e, HttpServletRequest req) {
String errorMessage = e.getMessage();
logError(req, "参数异常", e);
return CommonResult.validateFailed(errorMessage);
return CommonResult.validateFailed("数据格式输入有误,请检查!");
}
/**
@ -100,7 +100,7 @@ public class GlobalExceptionHandler {
if (e instanceof SQLException || e instanceof DataAccessException) {
return CommonResult.failed(DB_ERROR_MSG);
}
return CommonResult.failed(e.getMessage());
return CommonResult.failed("系统内部异常,请联系管理员!");
}
/**

View File

@ -61,9 +61,9 @@ public class LklSeparateDTO {
// 测试基于可分账金额的分账算法正常情况
System.out.println("\n=== 基于可分账金额的分账算法测试(正常情况) ===");
LklSeparateDTO dto2 = new LklSeparateDTO();
dto2.setTotalSeparateAmount(990); // 分账总额 1000分
dto2.setShippingFee(500); // 配送费 100分
dto2.setRefCanSeparateAmount(null);
dto2.setTotalSeparateAmount(2); // 分账总额 1000分
dto2.setShippingFee(1); // 配送费 100分
// dto2.setRefCanSeparateAmount(null);
dto2.setLklRatio(new BigDecimal("0.0025")); // 拉卡拉分账比例 0.0025
dto2.setMchRatio(new BigDecimal("0.94")); // 商家分账比例 0.857 (会产生小数)
dto2.setPlatRatio(new BigDecimal("0.01")); // 平台分账比例 0.01
@ -75,7 +75,6 @@ public class LklSeparateDTO {
System.out.println(result2);
if (result2.isSuccess()) {
printSharingDetails(dto2, "基于可分账金额");
}
// 测试toJSON和toString方法

View File

@ -17,10 +17,24 @@ public class UpdateOrderSeparateJob extends QuartzJobBean {
@Override
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
LakalaApiService lakalaApiService = SpringUtil.getBean(LakalaApiService.class);
logger.info("[分账状态修复定时任务] 开始执行");
// lakalaApiService.fixedOrderSeparateStatus();
try {
LakalaApiService lakalaApiService = SpringUtil.getBean(LakalaApiService.class);
if (lakalaApiService == null) {
logger.error("[分账状态修复定时任务] 无法获取LakalaApiService实例");
return;
}
Integer processedCount = lakalaApiService.fixedUnSuccessSeparateStatusJob();
logger.info("[分账状态修复定时任务] 执行完成,共处理 {} 条记录", processedCount);
} catch (Exception e) {
logger.error("[分账状态修复定时任务] 执行过程中发生异常", e);
throw new JobExecutionException(e);
}
}
}

View File

@ -10,11 +10,10 @@ import org.springframework.stereotype.Service;
@Service
public class QuartzServiceImpl implements QuartzService {
private final String PATH_PREFIX = "com.suisung.mall.shop.components.quartz.job.";
@Autowired
private Scheduler scheduler;
private final String PATH_PREFIX = "com.suisung.mall.shop.components.quartz.job.";
/**
* 新增一个定时任务
*
@ -27,21 +26,60 @@ public class QuartzServiceImpl implements QuartzService {
*/
@Override
public void addJob(String jName, String jGroup, String tName, String tGroup, String cron, String cName) {
// 1. 参数校验
if (jName == null || jName.trim().isEmpty()) {
throw new ApiException(I18nUtil._("任务名称不能为空!"));
}
if (jGroup == null || jGroup.trim().isEmpty()) {
throw new ApiException(I18nUtil._("任务组不能为空!"));
}
if (tName == null || tName.trim().isEmpty()) {
throw new ApiException(I18nUtil._("触发器名称不能为空!"));
}
if (tGroup == null || tGroup.trim().isEmpty()) {
throw new ApiException(I18nUtil._("触发器组不能为空!"));
}
if (cron == null || cron.trim().isEmpty()) {
throw new ApiException(I18nUtil._("cron表达式不能为空"));
}
if (cName == null || cName.trim().isEmpty()) {
throw new ApiException(I18nUtil._("任务实现类名称不能为空!"));
}
Class<Job> clazz = null;
// 2. 加载任务类
Class<Job> clazz;
try {
clazz = (Class<Job>) Class.forName(PATH_PREFIX + cName);
} catch (ClassNotFoundException e) {
throw new ApiException(I18nUtil._("定时任务脚本不存在!"));
throw new ApiException(I18nUtil._("定时任务脚本不存在!类名: " + PATH_PREFIX + cName), e);
}
try {
JobDetail jobDetail = JobBuilder.newJob(clazz).withIdentity(jName, jGroup).build();
CronTrigger trigger = TriggerBuilder.newTrigger().withIdentity(tName, tGroup).startNow().withSchedule(CronScheduleBuilder.cronSchedule(cron)).build();
scheduler.start();
// 3. 构建任务详情和触发器
JobDetail jobDetail = JobBuilder.newJob(clazz)
.withIdentity(jName, jGroup)
.build();
CronTrigger trigger = TriggerBuilder.newTrigger()
.withIdentity(tName, tGroup)
.startNow()
.withSchedule(CronScheduleBuilder.cronSchedule(cron))
.build();
// 4. 检查任务是否已存在
if (scheduler.checkExists(jobDetail.getKey())) {
scheduler.deleteJob(jobDetail.getKey());
}
// 5. 调度任务
scheduler.scheduleJob(jobDetail, trigger);
} catch (Exception e) {
throw new ApiException(I18nUtil._("添加定时任务失败!"));
// 6. 启动调度器如果尚未启动
if (!scheduler.isStarted()) {
scheduler.start();
}
} catch (SchedulerException e) {
throw new ApiException(I18nUtil._("添加定时任务失败!"), e);
}
}

View File

@ -280,4 +280,11 @@ public interface LakalaApiService {
*/
Boolean sacsSeparateCancel(String merchantNo, String originSeparateNo, String originOutSeparateNo, String outSeparateNo, String totalAmt);
/**
* 更改已经分账的状态定时任务用途
*
* @return
*/
Integer fixedUnSuccessSeparateStatusJob();
}

View File

@ -64,11 +64,14 @@ public interface LklOrderSeparateService extends IBaseService<LklOrderSeparate>
/**
* 查询未成功分账的记录
* 分页获取未成功分账的记录
*
* @param
* @return
* @param beginDate 开始时间必须
* @param endDate 结束时间可选为空时默认为当前时间
* @param page 页码从1开始
* @param size 每页大小
* @return 未成功分账的记录列表参数无效时返回空列表而非null
*/
List<LklOrderSeparate> unSuccessSeparateList(Date beginDate, Date endDate);
List<LklOrderSeparate> getUnSuccessSeparateList(Date beginDate, Date endDate, Integer page, Integer size);
}

View File

@ -34,6 +34,7 @@ import com.suisung.mall.common.modules.store.ShopStoreBase;
import com.suisung.mall.common.pojo.dto.LklSeparateDTO;
import com.suisung.mall.common.service.impl.CommonService;
import com.suisung.mall.common.utils.*;
import com.suisung.mall.core.web.service.RedisService;
import com.suisung.mall.shop.lakala.service.*;
import com.suisung.mall.shop.lakala.utils.LakalaUtil;
import com.suisung.mall.shop.message.service.PushMessageService;
@ -44,6 +45,7 @@ import com.suisung.mall.shop.page.service.OssService;
import com.suisung.mall.shop.store.service.ShopMchEntryService;
import com.suisung.mall.shop.store.service.ShopStoreBaseService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.time.DateUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Lazy;
@ -60,6 +62,7 @@ import java.math.BigDecimal;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
@ -152,6 +155,10 @@ public class LakalaApiServiceImpl implements LakalaApiService {
@Resource
private PushMessageService pushMessageService;
@Lazy
@Resource
private RedisService redisService;
@Lazy
@Resource
private OssService ossService;
@ -2683,4 +2690,120 @@ public class LakalaApiServiceImpl implements LakalaApiService {
}
}
/**
* 更改已经分账的状态定时任务用途
* 该方法用于处理3天前未成功分账的订单记录通过主动查询拉卡拉分账状态来更新本地记录
* 每条记录最多处理5次避免无限重试
*
* @return 成功处理的记录数量
*/
@Override
public Integer fixedUnSuccessSeparateStatusJob() {
log.info("[分账状态修复任务] 开始执行未成功分账记录的状态修复任务");
// 获取3天前分账状态未成功的记录
Date now = new Date();
Date threeDaysAgo = DateUtils.addHours(now, -72);
// 分页参数
int pageSize = 100; // 每页处理100条记录
int currentPage = 1;
int totalSuccessCount = 0;
int totalProcessed = 0;
// 记录处理开始时间
long startTime = System.currentTimeMillis();
List<LklOrderSeparate> lklOrderSeparates;
do {
// 分页获取未成功分账的记录
lklOrderSeparates = lklOrderSeparateService.getUnSuccessSeparateList(threeDaysAgo, now, currentPage, pageSize);
if (CollectionUtil.isEmpty(lklOrderSeparates)) {
break;
}
log.info("[分账状态修复任务] 获取到第{}页数据,共{}条记录", currentPage, lklOrderSeparates.size());
String redisPrefKey = "lkl:separate:status:retry:";
// 按处理次数排序从未处理过的记录优先处理处理次数越多优先级越低
lklOrderSeparates.sort((o1, o2) -> {
String redisKey1 = redisPrefKey + o1.getSeparate_no();
String redisKey2 = redisPrefKey + o2.getSeparate_no();
int retryCount1 = Convert.toInt(redisService.get(redisKey1), 0);
int retryCount2 = Convert.toInt(redisService.get(redisKey2), 0);
return Integer.compare(retryCount1, retryCount2);
});
int batchSuccessCount = 0;
// 处理当前页中的记录
for (LklOrderSeparate lklOrderSeparate : lklOrderSeparates) {
// 检查该记录的处理次数是否已达到上限5次
String redisKey = redisPrefKey + lklOrderSeparate.getSeparate_no();
int retryCount = Convert.toInt(redisService.get(redisKey), 0);
if (retryCount >= 5) {
log.warn("[分账状态修复任务] 记录已达到最大重试次数,跳过处理: merchantNo={}, separateNo={}",
lklOrderSeparate.getMerchant_no(), lklOrderSeparate.getSeparate_no());
continue;
}
totalProcessed++;
log.info("[分账状态修复任务] 正在处理第 {} 条记录: merchantNo={}, separateNo={}, 已重试{}次",
totalProcessed, lklOrderSeparate.getMerchant_no(), lklOrderSeparate.getSeparate_no(), retryCount);
try {
// 增加处理次数计数
redisService.incr(redisKey, 1);
// 设置3天过期时间
redisService.expire(redisKey, 3 * 24 * 60 * 60);
// 调用分账通知回调接口进行状态补偿
JSONObject notifyResp = sacsSeparateNotify(null, lklOrderSeparate.getMerchant_no(), lklOrderSeparate.getSeparate_no());
if (notifyResp != null && "SUCCESS".equals(notifyResp.getStr("code"))) {
batchSuccessCount++;
log.debug("[分账状态修复任务] 记录处理成功: merchantNo={}, separateNo={}",
lklOrderSeparate.getMerchant_no(), lklOrderSeparate.getSeparate_no());
} else {
String errorMsg = notifyResp != null ? notifyResp.getStr("message") : "未知错误";
log.warn("[分账状态修复任务] 记录处理失败: merchantNo={}, separateNo={}, errorMsg={}",
lklOrderSeparate.getMerchant_no(), lklOrderSeparate.getSeparate_no(), errorMsg);
}
} catch (Exception e) {
log.error("[分账状态修复任务] 处理记录时发生异常: merchantNo={}, separateNo={}",
lklOrderSeparate.getMerchant_no(), lklOrderSeparate.getSeparate_no(), e);
}
}
totalSuccessCount += batchSuccessCount;
log.info("[分账状态修复任务] 第{}页处理完成: 处理了 {} 条记录,成功 {} 条", currentPage, lklOrderSeparates.size(), batchSuccessCount);
// 如果当前页数据少于页面大小说明已经是最后一页
if (lklOrderSeparates.size() < pageSize) {
break;
}
currentPage++;
// 添加短暂延迟避免对系统造成过大压力
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
} while (!CollectionUtil.isEmpty(lklOrderSeparates));
long endTime = System.currentTimeMillis();
log.info("[分账状态修复任务] 任务执行完成,总共处理 {} 条记录,成功处理 {} 条记录,耗时 {} ms",
totalProcessed, totalSuccessCount, (endTime - startTime));
return totalSuccessCount;
}
}

View File

@ -11,6 +11,7 @@ package com.suisung.mall.shop.lakala.service.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.suisung.mall.common.modules.lakala.LklOrderSeparate;
import com.suisung.mall.core.web.service.impl.BaseServiceImpl;
import com.suisung.mall.shop.lakala.mapper.LklOrderSeparateMapper;
@ -154,18 +155,30 @@ public class LklOrderSeparateServiceImpl extends BaseServiceImpl<LklOrderSeparat
}
/**
* 查询未成功分账的记录
* 分页获取未成功分账的记录
*
* @param beginDate 开始时间必须
* @param endDate 结束时间可选为空时默认为当前时间
* @param page 页码从1开始
* @param size 每页大小
* @return 未成功分账的记录列表参数无效时返回空列表而非null
*/
@Override
public List<LklOrderSeparate> unSuccessSeparateList(Date beginDate, Date endDate) {
public List<LklOrderSeparate> getUnSuccessSeparateList(Date beginDate, Date endDate, Integer page, Integer size) {
// 1. 参数校验
if (beginDate == null) {
log.warn("[查询未成功分账记录] 开始时间不能为空");
return Collections.emptyList(); // 返回空列表而非null避免NPE
log.warn("[分页查询未成功分账记录] 开始时间不能为空");
return Collections.emptyList();
}
if (page == null || page < 1) {
log.warn("[分页查询未成功分账记录] 页码必须大于0当前页码: {}", page);
return Collections.emptyList();
}
if (size == null || size <= 0) {
log.warn("[分页查询未成功分账记录] 页面大小必须大于0当前大小: {}", size);
return Collections.emptyList();
}
// 2. 处理结束时间默认值
@ -173,30 +186,32 @@ public class LklOrderSeparateServiceImpl extends BaseServiceImpl<LklOrderSeparat
// 3. 参数合理性校验
if (beginDate.after(actualEndDate)) {
log.warn("[查询未成功分账记录] 开始时间{}不能晚于结束时间{}", beginDate, actualEndDate);
log.warn("[分页查询未成功分账记录] 开始时间{}不能晚于结束时间{}", beginDate, actualEndDate);
return Collections.emptyList();
}
try {
// 4. 构造查询条件
QueryWrapper<LklOrderSeparate> queryWrapper = new QueryWrapper<>();
queryWrapper
queryWrapper.select("id", "merchant_no", "separate_no", "status", "final_status", "created_at")
.ne("status", "SUCCESS")
.ne("final_status", "SUCCESS")
.ge("created_at", beginDate)
.le("created_at", actualEndDate)
.orderByDesc("id");
// 5. 执行查询
List<LklOrderSeparate> result = list(queryWrapper);
// 5. 执行分页查询
Page<LklOrderSeparate> resultPage = page(new Page<>(page, size), queryWrapper);
List<LklOrderSeparate> result = resultPage.getRecords();
log.info("[查询未成功分账记录] 查询完成,开始时间={},结束时间={},结果数量={}",
beginDate, actualEndDate, (result != null ? result.size() : 0));
log.info("[分页查询未成功分账记录] 查询完成,开始时间={},结束时间={},页码={},页面大小={},结果数量={}",
beginDate, actualEndDate, page, size, (result != null ? result.size() : 0));
return result != null ? result : Collections.emptyList();
} catch (Exception e) {
log.error("[查询未成功分账记录] 查询过程中发生异常,开始时间={},结束时间={}", beginDate, actualEndDate, e);
return Collections.emptyList(); // 发生异常时返回空列表而非null
log.error("[分页查询未成功分账记录] 查询过程中发生异常,开始时间={},结束时间={},页码={},页面大小={}",
beginDate, endDate, page, size, e);
return Collections.emptyList();
}
}
}

View File

@ -894,7 +894,7 @@ public class SFExpressApiServiceImpl implements SFExpressApiService {
payload.put("category", CommonConstant.PUSH_MSG_CATE_MCH_ORDER_DETAIL);
payload.put("orderId", shopOrderId);
pushMessageService.noticeMerchantEmployeeOrderAction(null, shopOrderId, "您有一笔取消订单", "您有一笔取消订单[" + shopOrderId + "],请及时处理。", payload);
logger.info("[顺丰订单取消回调通知] 处理完成: shopOrderId={} sfOrderId={}", shopOrderId, sfOrderId);
return new ThirdApiRes().success("success");
} catch (Exception e) {
@ -987,6 +987,8 @@ public class SFExpressApiServiceImpl implements SFExpressApiService {
orderIsOutStatus = 0;
orderIsShippedStatus = StateCode.ORDER_SHIPPED_STATE_YES; // 已发货
pushRemark = "配送员已取货。";
// 上传发货信息到微信
wxOrderShippingService.uploadShippingInfoToWx(2, shopOrderId);
} else if (shopStoreSfOrder.getOrder_status().equals(StateCode.SF_ORDER_STATUS_FINISH)) {
// 顺丰同城状态17-配送员妥投完单;
// orderStatus = StateCode.ORDER_STATE_FINISH;

View File

@ -121,4 +121,5 @@ job:
- "UpdatePayCardStateJob"
- "RetryMqMsgJob"
- "ProductItemAutoFillJob"
- "XcxSubSendMessageJob"
- "XcxSubSendMessageJob"
- "UpdateOrderSeparateJob"