Merge branch '20260204'

# Conflicts:
#	.idea/compiler.xml
#	.idea/vcs.xml
#	lidee-admin/src/main/resources/application-local.yaml
#	lidee-admin/target/classes/application-local.yaml
#	lidee-admin/target/lidee-admin.jar
#	lidee-admin/target/lidee-admin.jar.original
#	lidee-admin/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
#	lidee-core/src/main/java/com/lideeyunji/core/framework/entity/ReportFieldEntity.java
#	lidee-core/src/main/java/com/lideeyunji/core/framework/service/impl/ReportServiceImpl.java
#	lidee-core/target/classes/com/lideeyunji/core/framework/entity/ReportFieldEntity.class
#	lidee-core/target/classes/com/lideeyunji/core/framework/service/impl/ReportServiceImpl.class
#	lidee-core/target/lidee-core-2.2.4.jar
#	lidee-core/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
#	lidee-module/lidee-module-api/target/lidee-module-api-2.2.4.jar
#	lidee-module/lidee-module-api/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
#	lidee-module/lidee-module-biz/target/lidee-module-biz-2.2.4.jar
#	lidee-module/lidee-module-biz/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
#	lidee-service/lidee-service-infra-api/target/lidee-service-infra-api-2.2.4.jar
#	lidee-service/lidee-service-infra-biz/target/lidee-service-infra-biz-2.2.4.jar
#	lidee-service/lidee-service-system-api/target/lidee-service-system-api-2.2.4.jar
#	lidee-service/lidee-service-system-biz/target/lidee-service-system-biz-2.2.4.jar
#	lidee-tool/tool-common/target/tool-common-2.2.4.jar
#	lidee-tool/tool-spring-boot-starter-ai/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
#	lidee-tool/tool-spring-boot-starter-ai/target/tool-spring-boot-starter-ai-2.2.4.jar
#	lidee-tool/tool-spring-boot-starter-captcha/target/tool-spring-boot-starter-captcha-2.2.4.jar
#	lidee-tool/tool-spring-boot-starter-dict/target/tool-spring-boot-starter-dict-2.2.4.jar
#	lidee-tool/tool-spring-boot-starter-excel/target/tool-spring-boot-starter-excel-2.2.4.jar
#	lidee-tool/tool-spring-boot-starter-exception/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
#	lidee-tool/tool-spring-boot-starter-exception/target/tool-spring-boot-starter-exception-2.2.4.jar
#	lidee-tool/tool-spring-boot-starter-file/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
#	lidee-tool/tool-spring-boot-starter-file/target/tool-spring-boot-starter-file-2.2.4.jar
#	lidee-tool/tool-spring-boot-starter-flowable/target/tool-spring-boot-starter-flowable-2.2.4.jar
#	lidee-tool/tool-spring-boot-starter-ip/target/tool-spring-boot-starter-ip-2.2.4.jar
#	lidee-tool/tool-spring-boot-starter-job/target/tool-spring-boot-starter-job-2.2.4.jar
#	lidee-tool/tool-spring-boot-starter-monitor/target/tool-spring-boot-starter-monitor-2.2.4.jar
#	lidee-tool/tool-spring-boot-starter-mybatis/target/tool-spring-boot-starter-mybatis-2.2.4.jar
#	lidee-tool/tool-spring-boot-starter-operatelog/target/tool-spring-boot-starter-operatelog-2.2.4.jar
#	lidee-tool/tool-spring-boot-starter-permission/target/tool-spring-boot-starter-permission-2.2.4.jar
#	lidee-tool/tool-spring-boot-starter-protection/target/tool-spring-boot-starter-protection-2.2.4.jar
#	lidee-tool/tool-spring-boot-starter-redis/target/tool-spring-boot-starter-redis-2.2.4.jar
#	lidee-tool/tool-spring-boot-starter-security/target/tool-spring-boot-starter-security-2.2.4.jar
#	lidee-tool/tool-spring-boot-starter-sql/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
#	lidee-tool/tool-spring-boot-starter-sql/target/tool-spring-boot-starter-sql-2.2.4.jar
#	lidee-tool/tool-spring-boot-starter-tenant/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
#	lidee-tool/tool-spring-boot-starter-tenant/target/tool-spring-boot-starter-tenant-2.2.4.jar
#	lidee-tool/tool-spring-boot-starter-test/target/tool-spring-boot-starter-test-2.2.4.jar
#	lidee-tool/tool-spring-boot-starter-web/target/tool-spring-boot-starter-web-2.2.4.jar
#	lidee-tool/tool-spring-boot-starter-websocket/target/tool-spring-boot-starter-websocket-2.2.4.jar
#	lidee-tool/tool-spring-boot-starter-yunji/target/maven-status/maven-compiler-plugin/compile/default-compile/inputFiles.lst
#	lidee-tool/tool-spring-boot-starter-yunji/target/tool-spring-boot-starter-yunji-2.2.4.jar
#	logs/lideeyunji-error.log
#	logs/lideeyunji-info.log
This commit is contained in:
chy
2026-02-09 23:30:06 +08:00
1718 changed files with 66 additions and 116884 deletions

View File

@@ -1,31 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.lideeyunji</groupId>
<artifactId>lidee-tool</artifactId>
<version>2.2.4</version>
</parent>
<groupId>com.lideeyunji</groupId>
<artifactId>tool-spring-boot-starter-job</artifactId>
<version>2.2.4</version>
<name>${project.artifactId}</name>
<description>任务拓展
1. 定时任务,基于 Quartz 拓展
2. 异步任务,基于 Spring Async 拓展</description>
<dependencies>
<dependency>
<groupId>com.lideeyunji</groupId>
<artifactId>tool-common</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -1,41 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>com.lideeyunji</groupId>
<artifactId>lidee-tool</artifactId>
<version>${lidee.version}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>tool-spring-boot-starter-job</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>任务拓展
1. 定时任务,基于 Quartz 拓展
2. 异步任务,基于 Spring Async 拓展
</description>
<dependencies>
<dependency>
<groupId>com.lideeyunji</groupId>
<artifactId>tool-common</artifactId>
</dependency>
<!-- Job 定时任务相关 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
<!-- 工具类相关 -->
<dependency>
<groupId>jakarta.validation</groupId>
<artifactId>jakarta.validation-api</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -1,36 +0,0 @@
package com.lideeyunji.tool.framework.quartz.config;
import com.alibaba.ttl.TtlRunnable;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
/**
* 异步任务 Configuration
*/
@AutoConfiguration
@EnableAsync
public class AsyncAutoConfiguration {
@Bean
public BeanPostProcessor threadPoolTaskExecutorBeanPostProcessor() {
return new BeanPostProcessor() {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (!(bean instanceof ThreadPoolTaskExecutor)) {
return bean;
}
// 修改提交的任务,接入 TransmittableThreadLocal
ThreadPoolTaskExecutor executor = (ThreadPoolTaskExecutor) bean;
executor.setTaskDecorator(TtlRunnable::get);
return executor;
}
};
}
}

View File

@@ -1,14 +0,0 @@
package com.lideeyunji.tool.framework.quartz.core.enums;
/**
* Quartz Job Data 的 key 枚举
*/
public enum JobDataKeyEnum {
JOB_ID,
JOB_HANDLER_NAME,
JOB_HANDLER_PARAM,
JOB_RETRY_COUNT, // 最大重试次数
JOB_RETRY_INTERVAL, // 每次重试间隔
}

View File

@@ -1,19 +0,0 @@
package com.lideeyunji.tool.framework.quartz.core.handler;
/**
* 任务处理器
*
* @author 金灯剑客
*/
public interface JobHandler {
/**
* 执行任务
*
* @param param 参数
* @return 结果
* @throws Exception 异常
*/
String execute(String param) throws Exception;
}

View File

@@ -1,113 +0,0 @@
package com.lideeyunji.tool.framework.quartz.core.handler;
import cn.hutool.core.date.LocalDateTimeUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.thread.ThreadUtil;
import com.lideeyunji.tool.framework.quartz.core.enums.JobDataKeyEnum;
import com.lideeyunji.tool.framework.quartz.core.service.JobLogFrameworkService;
import lombok.extern.slf4j.Slf4j;
import org.quartz.DisallowConcurrentExecution;
import org.quartz.JobExecutionContext;
import org.quartz.JobExecutionException;
import org.quartz.PersistJobDataAfterExecution;
import org.springframework.context.ApplicationContext;
import org.springframework.scheduling.quartz.QuartzJobBean;
import javax.annotation.Resource;
import java.time.LocalDateTime;
import static cn.hutool.core.exceptions.ExceptionUtil.getRootCauseMessage;
/**
* 基础 Job 调用者,负责调用 {@link JobHandler#execute(String)} 执行任务
*
* @author 金灯剑客
*/
@DisallowConcurrentExecution
@PersistJobDataAfterExecution
@Slf4j
public class JobHandlerInvoker extends QuartzJobBean {
@Resource
private ApplicationContext applicationContext;
@Resource
private JobLogFrameworkService jobLogFrameworkService;
@Override
protected void executeInternal(JobExecutionContext executionContext) throws JobExecutionException {
// 第一步,获得 Job 数据
Long jobId = executionContext.getMergedJobDataMap().getLong(JobDataKeyEnum.JOB_ID.name());
String jobHandlerName = executionContext.getMergedJobDataMap().getString(JobDataKeyEnum.JOB_HANDLER_NAME.name());
String jobHandlerParam = executionContext.getMergedJobDataMap().getString(JobDataKeyEnum.JOB_HANDLER_PARAM.name());
int refireCount = executionContext.getRefireCount();
int retryCount = (Integer) executionContext.getMergedJobDataMap().getOrDefault(JobDataKeyEnum.JOB_RETRY_COUNT.name(), 0);
int retryInterval = (Integer) executionContext.getMergedJobDataMap().getOrDefault(JobDataKeyEnum.JOB_RETRY_INTERVAL.name(), 0);
// 第二步,执行任务
Long jobLogId = null;
LocalDateTime startTime = LocalDateTime.now();
String data = null;
Throwable exception = null;
try {
// 记录 Job 日志(初始)
jobLogId = jobLogFrameworkService.createJobLog(jobId, startTime, jobHandlerName, jobHandlerParam, refireCount + 1);
// 执行任务
data = this.executeInternal(jobHandlerName, jobHandlerParam);
} catch (Throwable ex) {
exception = ex;
}
// 第三步,记录执行日志
this.updateJobLogResultAsync(jobLogId, startTime, data, exception, executionContext);
// 第四步,处理有异常的情况
handleException(exception, refireCount, retryCount, retryInterval);
}
private String executeInternal(String jobHandlerName, String jobHandlerParam) throws Exception {
// 获得 JobHandler 对象
JobHandler jobHandler = applicationContext.getBean(jobHandlerName, JobHandler.class);
Assert.notNull(jobHandler, "JobHandler 不会为空");
// 执行任务
return jobHandler.execute(jobHandlerParam);
}
private void updateJobLogResultAsync(Long jobLogId, LocalDateTime startTime, String data, Throwable exception,
JobExecutionContext executionContext) {
LocalDateTime endTime = LocalDateTime.now();
// 处理是否成功
boolean success = exception == null;
if (!success) {
data = getRootCauseMessage(exception);
}
// 更新日志
try {
jobLogFrameworkService.updateJobLogResultAsync(jobLogId, endTime, (int) LocalDateTimeUtil.between(startTime, endTime).toMillis(), success, data);
} catch (Exception ex) {
log.error("[executeInternal][Job({}) logId({}) 记录执行日志失败({}/{})]",
executionContext.getJobDetail().getKey(), jobLogId, success, data);
}
}
private void handleException(Throwable exception,
int refireCount, int retryCount, int retryInterval) throws JobExecutionException {
// 如果有异常,则进行重试
if (exception == null) {
return;
}
// 情况一:如果到达重试上限,则直接抛出异常即可
if (refireCount >= retryCount) {
throw new JobExecutionException(exception);
}
// 情况二:如果未到达重试上限,则 sleep 一定间隔时间,然后重试
// 这里使用 sleep 来实现,主要还是希望实现比较简单。因为,同一时间,不会存在大量失败的 Job。
if (retryInterval > 0) {
ThreadUtil.sleep(retryInterval);
}
// 第二个参数refireImmediately = true表示立即重试
throw new JobExecutionException(exception, true);
}
}

View File

@@ -1,43 +0,0 @@
package com.lideeyunji.tool.framework.quartz.core.service;
import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull;
import java.time.LocalDateTime;
/**
* Job 日志 Framework Service 接口
*
* @author 金灯剑客
*/
public interface JobLogFrameworkService {
/**
* 创建 Job 日志
*
* @param jobId 任务编号
* @param beginTime 开始时间
* @param jobHandlerName Job 处理器的名字
* @param jobHandlerParam Job 处理器的参数
* @param executeIndex 第几次执行
* @return Job 日志的编号
*/
Long createJobLog(@NotNull(message = "任务编号不能为空") Long jobId,
@NotNull(message = "开始时间") LocalDateTime beginTime,
@NotEmpty(message = "Job 处理器的名字不能为空") String jobHandlerName,
String jobHandlerParam,
@NotNull(message = "第几次执行不能为空") Integer executeIndex);
/**
* 更新 Job 日志的执行结果
*
* @param logId 日志编号
* @param endTime 结束时间。因为是异步,避免记录时间不准去
* @param duration 运行时长,单位:毫秒
* @param success 是否成功
* @param result 成功数据
*/
void updateJobLogResultAsync(@NotNull(message = "日志编号不能为空") Long logId,
@NotNull(message = "结束时间不能为空") LocalDateTime endTime,
@NotNull(message = "运行时长不能为空") Integer duration,
boolean success, String result);
}

View File

@@ -1,56 +0,0 @@
package com.lideeyunji.tool.framework.quartz.core.util;
import cn.hutool.core.date.LocalDateTimeUtil;
import org.quartz.CronExpression;
import java.text.ParseException;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
/**
* Quartz Cron 表达式的工具类
*
* @author 金灯剑客
*/
public class CronUtils {
/**
* 校验 CRON 表达式是否有效
*
* @param cronExpression CRON 表达式
* @return 是否有效
*/
public static boolean isValid(String cronExpression) {
return CronExpression.isValidExpression(cronExpression);
}
/**
* 基于 CRON 表达式,获得下 n 个满足执行的时间
*
* @param cronExpression CRON 表达式
* @param n 数量
* @return 满足条件的执行时间
*/
public static List<LocalDateTime> getNextTimes(String cronExpression, int n) {
// 获得 CronExpression 对象
CronExpression cron;
try {
cron = new CronExpression(cronExpression);
} catch (ParseException e) {
throw new IllegalArgumentException(e.getMessage());
}
// 从当前开始计算n 个满足条件的
Date now = new Date();
List<LocalDateTime> nextTimes = new ArrayList<>(n);
for (int i = 0; i < n; i++) {
Date nextTime = cron.getNextValidTimeAfter(now);
nextTimes.add(LocalDateTimeUtil.of(nextTime));
// 切换现在,为下一个触发时间;
now = nextTime;
}
return nextTimes;
}
}

View File

@@ -1,7 +0,0 @@
/**
* 1. 定时任务,采用 Quartz 实现进程内的任务执行。
* 考虑到高可用,使用 Quartz 自带的 MySQL 集群方案。
*
* 2. 异步任务,采用 Spring Async 异步执行。
*/
package com.lideeyunji.tool.framework.quartz;

View File

@@ -1,2 +0,0 @@
com.lideeyunji.tool.framework.quartz.config.QuartzAutoConfiguration
com.lideeyunji.tool.framework.quartz.config.AsyncAutoConfiguration