From 63ff212c802de64350aa995cef8cb12679813f79 Mon Sep 17 00:00:00 2001 From: Jack <46790855@qq.com> Date: Fri, 19 Sep 2025 18:12:52 +0800 Subject: [PATCH] =?UTF-8?q?=E9=80=80=E6=AC=BE=E7=8A=B6=E6=80=81=E9=97=AE?= =?UTF-8?q?=E9=A2=98=EF=BC=8C=E5=85=A8=E9=83=A8=E9=80=80=E6=AC=BE=EF=BC=8C?= =?UTF-8?q?=E5=B0=B1=E5=8F=96=E6=B6=88=E8=AE=A2=E5=8D=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../exception/GlobalExceptionHandler.java | 10 +- .../mall/common/pojo/dto/LklSeparateDTO.java | 7 +- .../quartz/job/UpdateOrderSeparateJob.java | 18 ++- .../service/impl/QuartzServiceImpl.java | 56 ++++++-- .../shop/lakala/service/LakalaApiService.java | 7 + .../service/LklOrderSeparateService.java | 11 +- .../service/impl/LakalaApiServiceImpl.java | 123 ++++++++++++++++++ .../impl/LklOrderSeparateServiceImpl.java | 39 ++++-- .../service/impl/SFExpressApiServiceImpl.java | 4 +- mall-shop/src/main/resources/application.yml | 3 +- 10 files changed, 240 insertions(+), 38 deletions(-) diff --git a/mall-common/src/main/java/com/suisung/mall/common/exception/GlobalExceptionHandler.java b/mall-common/src/main/java/com/suisung/mall/common/exception/GlobalExceptionHandler.java index ce493b0a..4756dae6 100644 --- a/mall-common/src/main/java/com/suisung/mall/common/exception/GlobalExceptionHandler.java +++ b/mall-common/src/main/java/com/suisung/mall/common/exception/GlobalExceptionHandler.java @@ -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("系统内部异常,请联系管理员!"); } /** diff --git a/mall-common/src/main/java/com/suisung/mall/common/pojo/dto/LklSeparateDTO.java b/mall-common/src/main/java/com/suisung/mall/common/pojo/dto/LklSeparateDTO.java index 46a00198..40de4841 100644 --- a/mall-common/src/main/java/com/suisung/mall/common/pojo/dto/LklSeparateDTO.java +++ b/mall-common/src/main/java/com/suisung/mall/common/pojo/dto/LklSeparateDTO.java @@ -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方法 diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/components/quartz/job/UpdateOrderSeparateJob.java b/mall-shop/src/main/java/com/suisung/mall/shop/components/quartz/job/UpdateOrderSeparateJob.java index 2a01e4b0..ccaf0825 100644 --- a/mall-shop/src/main/java/com/suisung/mall/shop/components/quartz/job/UpdateOrderSeparateJob.java +++ b/mall-shop/src/main/java/com/suisung/mall/shop/components/quartz/job/UpdateOrderSeparateJob.java @@ -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); + } } + } diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/components/quartz/service/impl/QuartzServiceImpl.java b/mall-shop/src/main/java/com/suisung/mall/shop/components/quartz/service/impl/QuartzServiceImpl.java index 34086f90..35e4c278 100644 --- a/mall-shop/src/main/java/com/suisung/mall/shop/components/quartz/service/impl/QuartzServiceImpl.java +++ b/mall-shop/src/main/java/com/suisung/mall/shop/components/quartz/service/impl/QuartzServiceImpl.java @@ -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 clazz = null; + // 2. 加载任务类 + Class clazz; try { clazz = (Class) 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); } } diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/LakalaApiService.java b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/LakalaApiService.java index 5fad2584..2a12c219 100644 --- a/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/LakalaApiService.java +++ b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/LakalaApiService.java @@ -280,4 +280,11 @@ public interface LakalaApiService { */ Boolean sacsSeparateCancel(String merchantNo, String originSeparateNo, String originOutSeparateNo, String outSeparateNo, String totalAmt); + /** + * 更改已经分账的状态(定时任务用途) + * + * @return + */ + Integer fixedUnSuccessSeparateStatusJob(); + } diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/LklOrderSeparateService.java b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/LklOrderSeparateService.java index 567499b6..50f2b330 100644 --- a/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/LklOrderSeparateService.java +++ b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/LklOrderSeparateService.java @@ -64,11 +64,14 @@ public interface LklOrderSeparateService extends IBaseService /** - * 查询未成功分账的记录 + * 分页获取未成功分账的记录 * - * @param - * @return + * @param beginDate 开始时间(必须) + * @param endDate 结束时间(可选,为空时默认为当前时间) + * @param page 页码(从1开始) + * @param size 每页大小 + * @return 未成功分账的记录列表,参数无效时返回空列表而非null */ - List unSuccessSeparateList(Date beginDate, Date endDate); + List getUnSuccessSeparateList(Date beginDate, Date endDate, Integer page, Integer size); } diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/impl/LakalaApiServiceImpl.java b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/impl/LakalaApiServiceImpl.java index 1f78f4af..d59c3ac8 100644 --- a/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/impl/LakalaApiServiceImpl.java +++ b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/impl/LakalaApiServiceImpl.java @@ -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 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; + } + } diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/impl/LklOrderSeparateServiceImpl.java b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/impl/LklOrderSeparateServiceImpl.java index 3afa46b8..7571e193 100644 --- a/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/impl/LklOrderSeparateServiceImpl.java +++ b/mall-shop/src/main/java/com/suisung/mall/shop/lakala/service/impl/LklOrderSeparateServiceImpl.java @@ -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 unSuccessSeparateList(Date beginDate, Date endDate) { + public List 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 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 result = list(queryWrapper); + // 5. 执行分页查询 + Page resultPage = page(new Page<>(page, size), queryWrapper); + List 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(); } } } diff --git a/mall-shop/src/main/java/com/suisung/mall/shop/sfexpress/service/impl/SFExpressApiServiceImpl.java b/mall-shop/src/main/java/com/suisung/mall/shop/sfexpress/service/impl/SFExpressApiServiceImpl.java index 322b6039..f0a215b4 100644 --- a/mall-shop/src/main/java/com/suisung/mall/shop/sfexpress/service/impl/SFExpressApiServiceImpl.java +++ b/mall-shop/src/main/java/com/suisung/mall/shop/sfexpress/service/impl/SFExpressApiServiceImpl.java @@ -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; diff --git a/mall-shop/src/main/resources/application.yml b/mall-shop/src/main/resources/application.yml index e2e7e167..dca587a2 100644 --- a/mall-shop/src/main/resources/application.yml +++ b/mall-shop/src/main/resources/application.yml @@ -121,4 +121,5 @@ job: - "UpdatePayCardStateJob" - "RetryMqMsgJob" - "ProductItemAutoFillJob" - - "XcxSubSendMessageJob" \ No newline at end of file + - "XcxSubSendMessageJob" + - "UpdateOrderSeparateJob" \ No newline at end of file