分账补偿任务

This commit is contained in:
Jack 2025-09-20 09:58:25 +08:00
parent c3a93737a0
commit df4e5c26b1
4 changed files with 240 additions and 49 deletions

View File

@ -11,6 +11,8 @@ import com.suisung.mall.core.web.service.impl.BaseServiceImpl;
import com.suisung.mall.shop.base.mapper.ShopBaseCrontabMapper;
import com.suisung.mall.shop.base.service.ShopBaseCrontabService;
import com.suisung.mall.shop.components.quartz.service.QuartzService;
import com.suisung.mall.shop.components.quartz.service.impl.QuartzServiceImpl;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@ -24,16 +26,16 @@ import org.springframework.transaction.annotation.Transactional;
* @author Xinze
* @since 2021-08-16
*/
@Slf4j
@Service
public class ShopBaseCrontabServiceImpl extends BaseServiceImpl<ShopBaseCrontabMapper, ShopBaseCrontab> implements ShopBaseCrontabService {
@Autowired
private QuartzService quartzService;
private final String JOB_NAME_PREFIX = "JOB_";
private final String TRIGGER_NAME_PREFIX = "TRIGGER_";
private final String JOB_GROUP_NAME = "DEFAULT_JOB_GROUP";
private final String TRIGGER_GROUP_NAME = "DEFAULT_TRIGGER_GROUP";
@Autowired
private QuartzService quartzService;
@Transactional
@Override
@ -48,7 +50,7 @@ public class ShopBaseCrontabServiceImpl extends BaseServiceImpl<ShopBaseCrontabM
throw new ApiException(ResultCode.FAILED);
}
if (CheckUtil.isNotEmpty(crontab_enable)) {
if (CheckUtil.isNotEmpty(crontab_enable) && crontab_enable == 1) {
addJob(shopBaseCrontab);
}
} else {
@ -58,9 +60,26 @@ public class ShopBaseCrontabServiceImpl extends BaseServiceImpl<ShopBaseCrontabM
throw new ApiException(ResultCode.FAILED);
}
quartzService.deleteJob(JOB_NAME_PREFIX + crontab_id, JOB_GROUP_NAME);
if (crontab_enable == 1) {
addJob(shopBaseCrontab);
try {
// 先尝试删除已存在的任务如果存在
quartzService.deleteJob(JOB_NAME_PREFIX + crontab_id, JOB_GROUP_NAME);
} catch (Exception e) {
// 删除失败记录日志但不中断操作
log.warn("删除旧任务时发生异常: 任务ID={}", crontab_id, e);
}
// 如果任务启用则添加新任务
if (crontab_enable != null && crontab_enable == 1) {
try {
addJob(shopBaseCrontab);
} catch (Exception e) {
log.error("添加新任务时发生异常: 任务ID={}", crontab_id, e);
// 如果添加失败尝试从数据库同步任务
boolean syncResult = syncSingleCrontabToScheduler(crontab_id);
if (!syncResult) {
throw new ApiException("添加定时任务失败,且同步任务也失败", e);
}
}
}
}
@ -72,7 +91,13 @@ public class ShopBaseCrontabServiceImpl extends BaseServiceImpl<ShopBaseCrontabM
public boolean removeCrontab(String crontab_id) {
boolean flag = remove(crontab_id);
if (flag) {
quartzService.deleteJob(JOB_NAME_PREFIX + crontab_id, JOB_GROUP_NAME);
try {
quartzService.deleteJob(JOB_NAME_PREFIX + crontab_id, JOB_GROUP_NAME);
} catch (Exception e) {
// 删除失败时尝试从数据库同步确保一致性
log.warn("删除任务时发生异常: 任务ID={}", crontab_id, e);
// 即使删除失败也继续执行避免阻塞业务流程
}
}
return flag;
}
@ -84,10 +109,19 @@ public class ShopBaseCrontabServiceImpl extends BaseServiceImpl<ShopBaseCrontabM
*/
@Override
public void addJob(ShopBaseCrontab shopBaseCrontab) {
String crontab_file = shopBaseCrontab.getCrontab_file();
Integer crontab_id = shopBaseCrontab.getCrontab_id();
String cron = getCron(shopBaseCrontab);
quartzService.addJob(JOB_NAME_PREFIX + crontab_id, JOB_GROUP_NAME, TRIGGER_NAME_PREFIX + crontab_id, TRIGGER_GROUP_NAME, cron, crontab_file);
try {
String crontab_file = shopBaseCrontab.getCrontab_file();
Integer crontab_id = shopBaseCrontab.getCrontab_id();
String cron = getCron(shopBaseCrontab);
quartzService.addJob(JOB_NAME_PREFIX + crontab_id, JOB_GROUP_NAME, TRIGGER_NAME_PREFIX + crontab_id, TRIGGER_GROUP_NAME, cron, crontab_file);
} catch (Exception e) {
log.error("添加定时任务失败,尝试从数据库同步: 任务ID={}", shopBaseCrontab.getCrontab_id(), e);
// 如果添加失败尝试从数据库同步任务
boolean syncResult = syncSingleCrontabToScheduler(shopBaseCrontab.getCrontab_id());
if (!syncResult) {
throw new ApiException("添加定时任务失败,且同步任务也失败", e);
}
}
}
/**
@ -111,12 +145,60 @@ public class ShopBaseCrontabServiceImpl extends BaseServiceImpl<ShopBaseCrontabM
throw new ApiException(I18nUtil._("每周和每月不能同时选择!"));
}
StringBuilder cron = new StringBuilder();
String space = " ";
cron.append("0").append(space).append(crontab_minute).append(space).append(crontab_hour).append(space).append(crontab_day).append(space).append(crontab_month).append(space).append(crontab_week);
return "0" + space + crontab_minute + space + crontab_hour + space + crontab_day + space + crontab_month + space + crontab_week;
}
return cron.toString();
/**
* 从数据库同步单个任务到调度器
*
* @param crontabId 任务ID
* @return 是否同步成功
*/
public boolean syncSingleCrontabToScheduler(Integer crontabId) {
try {
// 从数据库获取任务信息
ShopBaseCrontab crontab = getById(crontabId);
if (crontab == null) {
log.warn("未找到ID为{}的定时任务", crontabId);
return false;
}
// 检查任务是否启用
if (crontab.getCrontab_enable() == null || crontab.getCrontab_enable() != 1) {
log.info("任务ID为{}的定时任务未启用,无需同步到调度器", crontabId);
return true;
}
// 构造任务和触发器名称
String jobName = JOB_NAME_PREFIX + crontabId;
String jobGroup = JOB_GROUP_NAME;
String triggerName = TRIGGER_NAME_PREFIX + crontabId;
String triggerGroup = TRIGGER_GROUP_NAME;
// 生成cron表达式
String cron = getCron(crontab);
// 获取任务类名
String jobClass = crontab.getCrontab_file();
if (jobClass != null && jobClass.contains(".")) {
jobClass = jobClass.substring(0, jobClass.lastIndexOf("."));
}
// 通过QuartzService添加任务
if (quartzService instanceof QuartzServiceImpl) {
return ((QuartzServiceImpl) quartzService).addOrUpdateJobFromDatabase(
jobName, jobGroup, triggerName, triggerGroup, cron, jobClass);
} else {
// 兼容其他实现
quartzService.addJob(jobName, jobGroup, triggerName, triggerGroup, cron, jobClass);
return true;
}
} catch (Exception e) {
log.error("同步单个定时任务到调度器失败: 任务ID={}", crontabId, e);
return false;
}
}
}

View File

@ -1,15 +1,11 @@
package com.suisung.mall.shop.components.quartz.job;
import com.suisung.mall.common.utils.LogUtil;
import com.suisung.mall.shop.config.SpringUtil;
import com.suisung.mall.shop.order.service.ShopOrderReturnService;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.springframework.core.env.Environment;
import org.springframework.scheduling.quartz.QuartzJobBean;
import java.util.List;
/**
* 支付宝/微信退款 定时任务
*/
@ -22,20 +18,19 @@ public class UpdateOrderRefundJob extends QuartzJobBean {
String[] activeProfiles = environment.getActiveProfiles();
String activeProfile = activeProfiles[0];
if (!activeProfile.equals("prod")) {
return;
}
ShopOrderReturnService orderReturnService = SpringUtil.getBean(ShopOrderReturnService.class);
// 获取支付宝/微信退款订单
List<String> return_ids = orderReturnService.getOnlineRefundReturnId();
for (String return_id : return_ids) {
if (!orderReturnService.doOnlineRefund(return_id)) {
LogUtil.error(String.format("return_id: %s 原路退回出错", return_id));
// 处理异常标记
}
}
// ShopOrderReturnService orderReturnService = SpringUtil.getBean(ShopOrderReturnService.class);
//
// // 获取支付宝/微信退款订单
// List<String> return_ids = orderReturnService.getOnlineRefundReturnId();
// for (String return_id : return_ids) {
// if (!orderReturnService.doOnlineRefund(return_id)) {
// LogUtil.error(String.format("return_id: %s 原路退回出错", return_id));
//
// // 处理异常标记
// }
// }
}
}

View File

@ -4,12 +4,15 @@ import com.suisung.mall.common.exception.ApiException;
import com.suisung.mall.common.utils.I18nUtil;
import com.suisung.mall.shop.components.quartz.service.QuartzService;
import org.quartz.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
@Service
public class QuartzServiceImpl implements QuartzService {
private static final Logger logger = LoggerFactory.getLogger(QuartzServiceImpl.class);
private final String PATH_PREFIX = "com.suisung.mall.shop.components.quartz.job.";
@Autowired
private Scheduler scheduler;
@ -26,63 +29,126 @@ public class QuartzServiceImpl implements QuartzService {
*/
@Override
public void addJob(String jName, String jGroup, String tName, String tGroup, String cron, String cName) {
logger.info("[Quartz] 开始添加定时任务: 任务名称={}, 任务组={}, 触发器名称={}, 触发器组={}, cron表达式={}, 类名={}",
jName, jGroup, tName, tGroup, cron, cName);
// 1. 参数校验
if (jName == null || jName.trim().isEmpty()) {
logger.warn("[Quartz] 任务名称不能为空: {}", jName);
throw new ApiException(I18nUtil._("任务名称不能为空!"));
}
if (jGroup == null || jGroup.trim().isEmpty()) {
logger.warn("[Quartz] 任务组不能为空: {}", jGroup);
throw new ApiException(I18nUtil._("任务组不能为空!"));
}
if (tName == null || tName.trim().isEmpty()) {
logger.warn("[Quartz] 触发器名称不能为空: {}", tName);
throw new ApiException(I18nUtil._("触发器名称不能为空!"));
}
if (tGroup == null || tGroup.trim().isEmpty()) {
logger.warn("[Quartz] 触发器组不能为空: {}", tGroup);
throw new ApiException(I18nUtil._("触发器组不能为空!"));
}
if (cron == null || cron.trim().isEmpty()) {
logger.warn("[Quartz] cron表达式不能为空: {}", cron);
throw new ApiException(I18nUtil._("cron表达式不能为空"));
}
if (cName == null || cName.trim().isEmpty()) {
logger.warn("[Quartz] 任务实现类名称不能为空: {}", cName);
throw new ApiException(I18nUtil._("任务实现类名称不能为空!"));
}
// 2. 加载任务类
Class<Job> clazz;
try {
clazz = (Class<Job>) Class.forName(PATH_PREFIX + cName);
String fullClassName = PATH_PREFIX + cName;
logger.debug("[Quartz] 尝试加载任务类: {}", fullClassName);
clazz = (Class<Job>) Class.forName(fullClassName);
logger.debug("[Quartz] 成功加载任务类: {}", fullClassName);
} catch (ClassNotFoundException e) {
logger.error("[Quartz] 定时任务脚本不存在!类名: {}{}", PATH_PREFIX, cName, e);
throw new ApiException(I18nUtil._("定时任务脚本不存在!类名: " + PATH_PREFIX + cName), e);
}
try {
// 3. 构建任务详情和触发器
JobKey jobKey = new JobKey(jName, jGroup);
JobDetail jobDetail = JobBuilder.newJob(clazz)
.withIdentity(jName, jGroup)
.withIdentity(jobKey)
.build();
TriggerKey triggerKey = new TriggerKey(tName, tGroup);
CronTrigger trigger = TriggerBuilder.newTrigger()
.withIdentity(tName, tGroup)
.withIdentity(triggerKey)
.startNow()
.withSchedule(CronScheduleBuilder.cronSchedule(cron))
.build();
logger.debug("[Quartz] 构建任务详情和触发器完成: jobKey={}, triggerKey={}", jobKey, triggerKey);
// 4. 检查任务是否已存在
if (scheduler.checkExists(jobDetail.getKey())) {
scheduler.deleteJob(jobDetail.getKey());
if (scheduler.checkExists(jobKey)) {
logger.info("[Quartz] 任务已存在,先删除旧任务: jobKey={}", jobKey);
scheduler.deleteJob(jobKey);
}
// 5. 调度任务
logger.info("[Quartz] 开始调度任务: jobKey={}, triggerKey={}", jobKey, triggerKey);
scheduler.scheduleJob(jobDetail, trigger);
logger.info("[Quartz] 任务调度成功: jobKey={}, triggerKey={}", jobKey, triggerKey);
// 6. 启动调度器如果尚未启动
if (!scheduler.isStarted()) {
logger.info("[Quartz] 调度器尚未启动,正在启动...");
scheduler.start();
logger.info("[Quartz] 调度器启动成功");
}
} catch (SchedulerException e) {
logger.error("[Quartz] 添加定时任务失败!任务名称={}, 任务组={}, 触发器名称={}, 触发器组={}",
jName, jGroup, tName, tGroup, e);
throw new ApiException(I18nUtil._("添加定时任务失败!"), e);
} catch (Exception e) {
logger.error("[Quartz] 添加定时任务时发生未知异常!任务名称={}, 任务组={}, 触发器名称={}, 触发器组={}",
jName, jGroup, tName, tGroup, e);
throw new ApiException(I18nUtil._("添加定时任务失败!"), e);
}
}
/**
* 添加或更新任务如果调度器中不存在任务则从数据库获取信息添加
*
* @param jobName 任务名称
* @param jobGroup 任务组
* @param triggerName 触发器名称
* @param triggerGroup 触发器组
* @param dbCron 从数据库获取的cron表达式
* @param dbJobClass 从数据库获取的任务类名
* @return 是否添加成功
*/
public boolean addOrUpdateJobFromDatabase(String jobName, String jobGroup, String triggerName,
String triggerGroup, String dbCron, String dbJobClass) {
logger.info("[Quartz] 尝试从数据库信息添加或更新任务: 任务名称={}, 任务组={}", jobName, jobGroup);
try {
JobKey jobKey = new JobKey(jobName, jobGroup);
// 检查任务是否已存在
if (scheduler.checkExists(jobKey)) {
logger.debug("[Quartz] 任务已存在于调度器中: jobKey={}", jobKey);
return true; // 任务已存在无需添加
}
// 任务不存在从数据库信息创建新任务
logger.info("[Quartz] 调度器中不存在任务,从数据库信息创建: jobKey={}", jobKey);
addJob(jobName, jobGroup, triggerName, triggerGroup, dbCron, dbJobClass);
return true;
} catch (Exception e) {
logger.error("[Quartz] 从数据库信息添加或更新任务失败: 任务名称={}, 任务组={}", jobName, jobGroup, e);
return false;
}
}
/**
* 暂停定时任务
*
@ -91,10 +157,24 @@ public class QuartzServiceImpl implements QuartzService {
*/
@Override
public void pauseJob(String jName, String jGroup) {
logger.info("[Quartz] 开始暂停定时任务: 任务名称={}, 任务组={}", jName, jGroup);
try {
scheduler.pauseJob(JobKey.jobKey(jName, jGroup));
JobKey jobKey = JobKey.jobKey(jName, jGroup);
// 检查任务是否存在不存在则记录日志并返回不抛出异常
if (!scheduler.checkExists(jobKey)) {
logger.info("[Quartz] 任务不存在,无需暂停: jobKey={},可能任务已手动删除或尚未创建", jobKey);
return;
}
scheduler.pauseJob(jobKey);
logger.info("[Quartz] 定时任务暂停成功: jobKey={}", jobKey);
} catch (SchedulerException e) {
logger.error("[Quartz] 暂停定时任务时发生调度异常!任务名称={}, 任务组={}", jName, jGroup, e);
// 即使暂停失败也继续执行避免阻塞业务流程
} catch (Exception e) {
throw new ApiException(I18nUtil._("暂停定时任务失败!"));
logger.error("[Quartz] 暂停定时任务时发生未知异常!任务名称={}, 任务组={}", jName, jGroup, e);
// 即使暂停失败也继续执行避免阻塞业务流程
}
}
@ -106,10 +186,24 @@ public class QuartzServiceImpl implements QuartzService {
*/
@Override
public void resumeJob(String jName, String jGroup) {
logger.info("[Quartz] 开始继续定时任务: 任务名称={}, 任务组={}", jName, jGroup);
try {
scheduler.resumeJob(JobKey.jobKey(jName, jGroup));
JobKey jobKey = JobKey.jobKey(jName, jGroup);
// 检查任务是否存在不存在则记录日志并返回不抛出异常
if (!scheduler.checkExists(jobKey)) {
logger.info("[Quartz] 任务不存在,无需继续: jobKey={},可能任务已手动删除或尚未创建", jobKey);
return;
}
scheduler.resumeJob(jobKey);
logger.info("[Quartz] 定时任务继续成功: jobKey={}", jobKey);
} catch (SchedulerException e) {
throw new ApiException(I18nUtil._("继续定时任务失败!"));
logger.error("[Quartz] 继续定时任务时发生调度异常!任务名称={}, 任务组={}", jName, jGroup, e);
// 即使继续失败也继续执行避免阻塞业务流程
} catch (Exception e) {
logger.error("[Quartz] 继续定时任务时发生未知异常!任务名称={}, 任务组={}", jName, jGroup, e);
// 即使继续失败也继续执行避免阻塞业务流程
}
}
@ -121,10 +215,30 @@ public class QuartzServiceImpl implements QuartzService {
*/
@Override
public void deleteJob(String jName, String jGroup) {
logger.info("[Quartz] 开始删除定时任务: 任务名称={}, 任务组={}", jName, jGroup);
try {
scheduler.deleteJob(JobKey.jobKey(jName, jGroup));
JobKey jobKey = JobKey.jobKey(jName, jGroup);
// 检查任务是否存在不存在则记录日志并返回不抛出异常
if (!scheduler.checkExists(jobKey)) {
logger.info("[Quartz] 任务不存在,无需删除: jobKey={},可能任务已手动删除或尚未创建", jobKey);
return;
}
boolean deleted = scheduler.deleteJob(jobKey);
if (deleted) {
logger.info("[Quartz] 定时任务删除成功: jobKey={}", jobKey);
} else {
logger.warn("[Quartz] 定时任务删除失败: jobKey={}", jobKey);
}
} catch (SchedulerException e) {
throw new ApiException(I18nUtil._("删除定时任务失败!"));
logger.error("[Quartz] 删除定时任务时发生调度异常!任务名称={}, 任务组={}", jName, jGroup, e);
// 即使删除失败也继续执行避免阻塞业务流程
} catch (Exception e) {
logger.error("[Quartz] 删除定时任务时发生未知异常!任务名称={}, 任务组={}", jName, jGroup, e);
// 即使删除失败也继续执行避免阻塞业务流程
}
}
}

View File

@ -160,11 +160,11 @@ public class LklOrderSeparateServiceImpl extends BaseServiceImpl<LklOrderSeparat
* @param beginDate 开始时间必须
* @param endDate 结束时间可选为空时默认为当前时间
* @param page 页码从1开始
* @param size 每页大小
* @param pageSize 每页大小
* @return 未成功分账的记录列表参数无效时返回空列表而非null
*/
@Override
public List<LklOrderSeparate> getUnSuccessSeparateList(Date beginDate, Date endDate, Integer page, Integer size) {
public List<LklOrderSeparate> getUnSuccessSeparateList(Date beginDate, Date endDate, Integer page, Integer pageSize) {
// 1. 参数校验
if (beginDate == null) {
log.warn("[分页查询未成功分账记录] 开始时间不能为空");
@ -176,8 +176,8 @@ public class LklOrderSeparateServiceImpl extends BaseServiceImpl<LklOrderSeparat
return Collections.emptyList();
}
if (size == null || size <= 0) {
log.warn("[分页查询未成功分账记录] 页面大小必须大于0当前大小: {}", size);
if (pageSize == null || pageSize <= 0) {
log.warn("[分页查询未成功分账记录] 页面大小必须大于0当前大小: {}", pageSize);
return Collections.emptyList();
}
@ -201,16 +201,16 @@ public class LklOrderSeparateServiceImpl extends BaseServiceImpl<LklOrderSeparat
.orderByDesc("id");
// 5. 执行分页查询
Page<LklOrderSeparate> resultPage = page(new Page<>(page, size), queryWrapper);
Page<LklOrderSeparate> resultPage = lists(queryWrapper, page, pageSize);
List<LklOrderSeparate> result = resultPage.getRecords();
log.info("[分页查询未成功分账记录] 查询完成,开始时间={},结束时间={},页码={},页面大小={},结果数量={}",
beginDate, actualEndDate, page, size, (result != null ? result.size() : 0));
beginDate, actualEndDate, page, pageSize, (result != null ? result.size() : 0));
return result != null ? result : Collections.emptyList();
} catch (Exception e) {
log.error("[分页查询未成功分账记录] 查询过程中发生异常,开始时间={},结束时间={},页码={},页面大小={}",
beginDate, endDate, page, size, e);
beginDate, endDate, page, pageSize, e);
return Collections.emptyList();
}
}