serv-定时任务

This commit is contained in:
2025-10-25 18:46:54 +08:00
parent 5d211faee1
commit 485e1b8be4
29 changed files with 3391 additions and 6 deletions

View File

@@ -0,0 +1,59 @@
-- ====================================================
-- 定时任务表
-- ====================================================
DROP TABLE IF EXISTS `tb_crontab_task`;
CREATE TABLE `tb_crontab_task` (
`id` VARCHAR(64) NOT NULL COMMENT '主键ID',
`task_id` VARCHAR(64) NOT NULL COMMENT '任务ID',
`task_name` VARCHAR(100) NOT NULL COMMENT '任务名称',
`task_group` VARCHAR(50) NOT NULL DEFAULT 'DEFAULT' COMMENT '任务分组',
`bean_name` VARCHAR(100) NOT NULL COMMENT 'Bean名称',
`method_name` VARCHAR(100) NOT NULL COMMENT '方法名称',
`method_params` VARCHAR(500) DEFAULT NULL COMMENT '方法参数',
`cron_expression` VARCHAR(100) NOT NULL COMMENT 'Cron表达式',
`status` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '任务状态0:暂停 1:运行中)',
`description` VARCHAR(500) DEFAULT NULL COMMENT '任务描述',
`concurrent` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否允许并发执行0:否 1:是)',
`misfire_policy` TINYINT(1) NOT NULL DEFAULT 1 COMMENT '错过执行策略1:立即执行 2:执行一次 3:放弃执行)',
`creator` VARCHAR(64) DEFAULT NULL COMMENT '创建者',
`updater` VARCHAR(64) DEFAULT NULL COMMENT '更新者',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`delete_time` DATETIME DEFAULT NULL COMMENT '删除时间',
`deleted` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否删除0:否 1:是)',
PRIMARY KEY (`id`),
KEY `idx_task_name` (`task_name`),
KEY `idx_bean_name` (`bean_name`),
KEY `idx_status` (`status`),
KEY `idx_deleted` (`deleted`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci COMMENT='定时任务配置表';
-- ====================================================
-- 定时任务执行日志表
-- ====================================================
DROP TABLE IF EXISTS `tb_crontab_log`;
CREATE TABLE `tb_crontab_log` (
`id` VARCHAR(64) NOT NULL COMMENT '主键ID',
`task_id` VARCHAR(64) NOT NULL COMMENT '任务ID',
`task_name` VARCHAR(100) NOT NULL COMMENT '任务名称',
`task_group` VARCHAR(50) NOT NULL DEFAULT 'DEFAULT' COMMENT '任务分组',
`bean_name` VARCHAR(100) NOT NULL COMMENT 'Bean名称',
`method_name` VARCHAR(100) NOT NULL COMMENT '方法名称',
`method_params` VARCHAR(500) DEFAULT NULL COMMENT '方法参数',
`execute_status` TINYINT(1) NOT NULL COMMENT '执行状态0:失败 1:成功)',
`execute_message` TEXT DEFAULT NULL COMMENT '执行结果信息',
`exception_info` TEXT DEFAULT NULL COMMENT '异常信息',
`start_time` DATETIME NOT NULL COMMENT '开始时间',
`end_time` DATETIME DEFAULT NULL COMMENT '结束时间',
`execute_duration` INT DEFAULT NULL COMMENT '执行时长(毫秒)',
`create_time` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` DATETIME DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`delete_time` DATETIME DEFAULT NULL COMMENT '删除时间',
`deleted` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否删除0:否 1:是)',
PRIMARY KEY (`id`),
KEY `idx_task_id` (`task_id`),
KEY `idx_task_name` (`task_name`),
KEY `idx_execute_status` (`execute_status`),
KEY `idx_start_time` (`start_time`),
KEY `idx_deleted` (`deleted`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci COMMENT='定时任务执行日志表';

View File

@@ -37,6 +37,8 @@ SOURCE createTableSystem.sql;
SOURCE createTableAchievement.sql;
SOURCE createTableCrontab.sql;
-- =====================================================
-- 插入初始数据
-- =====================================================

View File

@@ -25,7 +25,8 @@ INSERT INTO `tb_sys_module` (id, module_id, name, code, description, icon, order
('3', 'module_study', '学习管理', 'study', '学习管理模块', 'el-icon-reading', 3, 1, '1', now()),
('4', 'module_ai', 'AI管理', 'ai', 'AI管理模块', 'el-icon-cpu', 4, 1, '1', now()),
('5', 'module_usercenter', '用户中心', 'usercenter', '用户中心模块', 'el-icon-user', 5, 1, '1', now()),
('6', 'module_file', '文件管理', 'file', '文件管理模块', 'el-icon-folder', 6, 1, '1', now());
('6', 'module_file', '文件管理', 'file', '文件管理模块', 'el-icon-folder', 6, 1, '1', now()),
('7', 'module_crontab', '定时任务', 'crontab', '定时任务管理模块', 'el-icon-alarm-clock', 7, 1, '1', now());
-- 插入权限数据
INSERT INTO `tb_sys_permission` (id,permission_id, name, code, description, module_id, creator, create_time) VALUES
@@ -43,7 +44,9 @@ INSERT INTO `tb_sys_permission` (id,permission_id, name, code, description, modu
('10.1','perm_achievement_manage', '成就管理', 'achievement:manage', '成就管理权限', 'module_study', '1', now()),
('11','perm_ai_manage', 'AI管理', 'ai:manage', 'AI管理权限', 'module_ai', '1', now()),
('12','perm_usercenter_manage', '用户中心管理', 'usercenter:manage', '用户中心管理权限', 'module_usercenter', '1', now()),
('13','perm_file_manage', '文件管理', 'file:manage', '文件管理权限', 'module_file', '1', now());
('13','perm_file_manage', '文件管理', 'file:manage', '文件管理权限', 'module_file', '1', now()),
('14','perm_crontab_manage', '定时任务管理', 'crontab:manage', '定时任务管理权限', 'module_crontab', '1', now()),
('15','perm_crontab_execute', '定时任务执行', 'crontab:execute', '定时任务执行权限', 'module_crontab', '1', now());
-- 插入角色-权限关联数据
INSERT INTO `tb_sys_role_permission` (id, role_id, permission_id, creator, create_time) VALUES
@@ -62,7 +65,11 @@ INSERT INTO `tb_sys_role_permission` (id, role_id, permission_id, creator, creat
('11', 'superadmin', 'perm_ai_manage', '1', now()),
('12', 'superadmin', 'perm_usercenter_manage', '1', now()),
('13', 'superadmin', 'perm_file_manage', '1', now()),
('14', 'freedom', 'perm_default', '1', now());
('14', 'freedom', 'perm_default', '1', now()),
('15', 'superadmin', 'perm_crontab_manage', '1', now()),
('16', 'superadmin', 'perm_crontab_execute', '1', now()),
('17', 'admin', 'perm_crontab_manage', '1', now()),
('18', 'admin', 'perm_crontab_execute', '1', now());
-- 插入前端菜单数据
INSERT INTO `tb_sys_menu` (id, menu_id, name, parent_id, url, component, icon, order_num, type, layout, creator, create_time) VALUES
@@ -138,7 +145,13 @@ INSERT INTO `tb_sys_menu` (id, menu_id, name, parent_id, url, component, icon, o
('7001', 'menu_admin_system_logs', '系统日志', 'menu_admin_logs_manage', '/admin/manage/logs/system', 'admin/manage/logs/SystemLogsView', 'el-icon-document', 1, 1, 'NavigationLayout', '1', now()),
('7002', 'menu_admin_login_logs', '登录日志', 'menu_admin_logs_manage', '/admin/manage/logs/login', 'admin/manage/logs/LoginLogsView', 'el-icon-key', 2, 1, 'NavigationLayout', '1', now()),
('7003', 'menu_admin_operation_logs', '操作日志', 'menu_admin_logs_manage', '/admin/manage/logs/operation', 'admin/manage/logs/OperationLogsView', 'el-icon-s-operation', 3, 1, 'NavigationLayout', '1', now()),
('7004', 'menu_admin_system_config', '系统配置', 'menu_admin_logs_manage', '/admin/manage/logs/config', 'admin/manage/logs/SystemConfigView', 'el-icon-setting', 4, 1, 'NavigationLayout', '1', now());
('7004', 'menu_admin_system_config', '系统配置', 'menu_admin_logs_manage', '/admin/manage/logs/config', 'admin/manage/logs/SystemConfigView', 'el-icon-setting', 4, 1, 'NavigationLayout', '1', now()),
-- 定时任务管理
('8000', 'menu_admin_crontab_manage', '定时任务管理', NULL, '', '', 'el-icon-alarm-clock', 8, 1, '', '1', now()),
('8001', 'menu_admin_crontab_task', '任务管理', 'menu_admin_crontab_manage', '/admin/manage/crontab/task', 'admin/manage/crontab/TaskManagementView', 'el-icon-s-order', 1, 1, 'NavigationLayout', '1', now()),
('8002', 'menu_admin_crontab_log', '执行日志', 'menu_admin_crontab_manage', '/admin/manage/crontab/log', 'admin/manage/crontab/LogManagementView', 'el-icon-document', 2, 1, 'NavigationLayout', '1', now()),
('8003', 'menu_admin_news_crawler', '新闻爬虫配置', 'menu_admin_crontab_manage', '/admin/manage/crontab/news-crawler', 'admin/manage/crontab/NewsCrawlerView', 'el-icon-share', 3, 1, 'NavigationLayout', '1', now());
-- 插入菜单权限关联数据
INSERT INTO `tb_sys_menu_permission` (id, permission_id, menu_id, creator, create_time) VALUES
@@ -195,4 +208,10 @@ INSERT INTO `tb_sys_menu_permission` (id, permission_id, menu_id, creator, creat
('228', 'perm_system_manage', 'menu_admin_system_logs', '1', now()),
('229', 'perm_system_manage', 'menu_admin_login_logs', '1', now()),
('230', 'perm_system_manage', 'menu_admin_operation_logs', '1', now()),
('231', 'perm_system_manage', 'menu_admin_system_config', '1', now());
('231', 'perm_system_manage', 'menu_admin_system_config', '1', now()),
-- 定时任务管理菜单权限关联
('232', 'perm_crontab_manage', 'menu_admin_crontab_manage', '1', now()),
('233', 'perm_crontab_manage', 'menu_admin_crontab_task', '1', now()),
('234', 'perm_crontab_manage', 'menu_admin_crontab_log', '1', now()),
('235', 'perm_crontab_manage', 'menu_admin_news_crawler', '1', now());

View File

@@ -69,6 +69,11 @@
<artifactId>ai</artifactId>
<version>${school-news.version}</version>
</dependency>
<dependency>
<groupId>org.xyzh</groupId>
<artifactId>crontab</artifactId>
<version>${school-news.version}</version>
</dependency>
</dependencies>
<build>

View File

@@ -3,6 +3,7 @@ package org.xyzh;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.transaction.annotation.EnableTransactionManagement;
/**
@@ -14,8 +15,9 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
*/
@EnableTransactionManagement
@SpringBootApplication(scanBasePackages = "org.xyzh")
@EnableScheduling
@MapperScan({"org.xyzh.system.mapper", "org.xyzh.file.mapper", "org.xyzh.news.mapper", "org.xyzh.study.mapper",
"org.xyzh.usercenter.mapper", "org.xyzh.ai.mapper", "org.xyzh.achievement.mapper"})
"org.xyzh.usercenter.mapper", "org.xyzh.ai.mapper", "org.xyzh.achievement.mapper", "org.xyzh.crontab.mapper"})
public class App {
public static void main(String[] args) {

View File

@@ -53,5 +53,9 @@
<groupId>org.xyzh</groupId>
<artifactId>api-file</artifactId>
</dependency>
<dependency>
<groupId>org.xyzh</groupId>
<artifactId>api-crontab</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,33 @@
<?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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.xyzh</groupId>
<artifactId>api</artifactId>
<version>${school-news.version}</version>
</parent>
<groupId>org.xyzh</groupId>
<artifactId>api-crontab</artifactId>
<version>${school-news.version}</version>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.xyzh</groupId>
<artifactId>common-core</artifactId>
<version>${school-news.version}</version>
</dependency>
<dependency>
<groupId>org.xyzh</groupId>
<artifactId>common-dto</artifactId>
<version>${school-news.version}</version>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,169 @@
package org.xyzh.api.crontab;
import org.xyzh.common.core.domain.ResultDomain;
import org.xyzh.common.core.page.PageParam;
import org.xyzh.common.dto.crontab.TbCrontabTask;
import org.xyzh.common.dto.crontab.TbCrontabLog;
import org.xyzh.common.vo.CrontabTaskVO;
import org.xyzh.common.vo.CrontabLogVO;
/**
* @description 定时任务服务接口
* @filename CrontabService.java
* @author yslg
* @copyright xyzh
* @since 2025-10-25
*/
public interface CrontabService {
// ----------------定时任务管理--------------------------------
/**
* @description 创建定时任务
* @param task 任务对象
* @return ResultDomain<TbCrontabTask> 创建结果
* @author yslg
* @since 2025-10-25
*/
ResultDomain<TbCrontabTask> createTask(TbCrontabTask task);
/**
* @description 更新定时任务
* @param task 任务对象
* @return ResultDomain<TbCrontabTask> 更新结果
* @author yslg
* @since 2025-10-25
*/
ResultDomain<TbCrontabTask> updateTask(TbCrontabTask task);
/**
* @description 删除定时任务
* @param taskId 任务ID
* @return ResultDomain<TbCrontabTask> 删除结果
* @author yslg
* @since 2025-10-25
*/
ResultDomain<TbCrontabTask> deleteTask(String taskId);
/**
* @description 根据ID查询任务
* @param taskId 任务ID
* @return ResultDomain<TbCrontabTask> 任务信息
* @author yslg
* @since 2025-10-25
*/
ResultDomain<TbCrontabTask> getTaskById(String taskId);
/**
* @description 根据过滤条件查询任务列表
* @param filter 过滤条件
* @return ResultDomain<TbCrontabTask> 任务列表
* @author yslg
* @since 2025-10-25
*/
ResultDomain<TbCrontabTask> getTaskList(TbCrontabTask filter);
/**
* @description 分页查询任务列表
* @param filter 过滤条件
* @param pageParam 分页参数
* @return ResultDomain<TbCrontabTask> 任务列表
* @author yslg
* @since 2025-10-25
*/
ResultDomain<TbCrontabTask> getTaskPage(TbCrontabTask filter, PageParam pageParam);
/**
* @description 启动定时任务
* @param taskId 任务ID
* @return ResultDomain<TbCrontabTask> 启动结果
* @author yslg
* @since 2025-10-25
*/
ResultDomain<TbCrontabTask> startTask(String taskId);
/**
* @description 暂停定时任务
* @param taskId 任务ID
* @return ResultDomain<TbCrontabTask> 暂停结果
* @author yslg
* @since 2025-10-25
*/
ResultDomain<TbCrontabTask> pauseTask(String taskId);
/**
* @description 立即执行一次任务
* @param taskId 任务ID
* @return ResultDomain<TbCrontabTask> 执行结果
* @author yslg
* @since 2025-10-25
*/
ResultDomain<TbCrontabTask> executeTaskOnce(String taskId);
/**
* @description 验证Cron表达式
* @param cronExpression Cron表达式
* @return ResultDomain<String> 验证结果(返回下次执行时间)
* @author yslg
* @since 2025-10-25
*/
ResultDomain<String> validateCronExpression(String cronExpression);
// ----------------定时任务日志--------------------------------
/**
* @description 根据任务ID查询执行日志
* @param taskId 任务ID
* @return ResultDomain<TbCrontabLog> 日志列表
* @author yslg
* @since 2025-10-25
*/
ResultDomain<TbCrontabLog> getLogsByTaskId(String taskId);
/**
* @description 根据过滤条件查询日志列表
* @param filter 过滤条件
* @return ResultDomain<TbCrontabLog> 日志列表
* @author yslg
* @since 2025-10-25
*/
ResultDomain<TbCrontabLog> getLogList(TbCrontabLog filter);
/**
* @description 分页查询日志列表
* @param filter 过滤条件
* @param pageParam 分页参数
* @return ResultDomain<TbCrontabLog> 日志列表
* @author yslg
* @since 2025-10-25
*/
ResultDomain<TbCrontabLog> getLogPage(TbCrontabLog filter, PageParam pageParam);
/**
* @description 根据ID查询日志详情
* @param logId 日志ID
* @return ResultDomain<TbCrontabLog> 日志详情
* @author yslg
* @since 2025-10-25
*/
ResultDomain<TbCrontabLog> getLogById(String logId);
/**
* @description 清理指定天数之前的日志
* @param days 天数
* @return ResultDomain<Integer> 清理结果(返回清理条数)
* @author yslg
* @since 2025-10-25
*/
ResultDomain<Integer> cleanLogs(Integer days);
/**
* @description 删除任务执行日志
* @param logId 日志ID
* @return ResultDomain<TbCrontabLog> 删除结果
* @author yslg
* @since 2025-10-25
*/
ResultDomain<TbCrontabLog> deleteLog(String logId);
}

View File

@@ -23,6 +23,7 @@
<module>api-study</module>
<module>api-news</module>
<module>api-file</module>
<module>api-crontab</module>
</modules>
<properties>
@@ -82,6 +83,11 @@
<artifactId>api-file</artifactId>
<version>${school-news.version}</version>
</dependency>
<dependency>
<groupId>org.xyzh</groupId>
<artifactId>api-crontab</artifactId>
<version>${school-news.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>

View File

@@ -0,0 +1,188 @@
package org.xyzh.common.dto.crontab;
import org.xyzh.common.dto.BaseDTO;
import java.util.Date;
/**
* @description 定时任务执行日志表
* @filename TbCrontabLog.java
* @author yslg
* @copyright xyzh
* @since 2025-10-25
*/
public class TbCrontabLog extends BaseDTO {
private static final long serialVersionUID = 1L;
/**
* @description 任务ID
*/
private String taskId;
/**
* @description 任务名称
*/
private String taskName;
/**
* @description 任务分组
*/
private String taskGroup;
/**
* @description Bean名称
*/
private String beanName;
/**
* @description 方法名称
*/
private String methodName;
/**
* @description 方法参数
*/
private String methodParams;
/**
* @description 执行状态0:失败 1:成功)
*/
private Integer executeStatus;
/**
* @description 执行结果信息
*/
private String executeMessage;
/**
* @description 异常信息
*/
private String exceptionInfo;
/**
* @description 开始时间
*/
private Date startTime;
/**
* @description 结束时间
*/
private Date endTime;
/**
* @description 执行时长(毫秒)
*/
private Integer executeDuration;
public String getTaskId() {
return taskId;
}
public void setTaskId(String taskId) {
this.taskId = taskId;
}
public String getTaskName() {
return taskName;
}
public void setTaskName(String taskName) {
this.taskName = taskName;
}
public String getTaskGroup() {
return taskGroup;
}
public void setTaskGroup(String taskGroup) {
this.taskGroup = taskGroup;
}
public String getBeanName() {
return beanName;
}
public void setBeanName(String beanName) {
this.beanName = beanName;
}
public String getMethodName() {
return methodName;
}
public void setMethodName(String methodName) {
this.methodName = methodName;
}
public String getMethodParams() {
return methodParams;
}
public void setMethodParams(String methodParams) {
this.methodParams = methodParams;
}
public Integer getExecuteStatus() {
return executeStatus;
}
public void setExecuteStatus(Integer executeStatus) {
this.executeStatus = executeStatus;
}
public String getExecuteMessage() {
return executeMessage;
}
public void setExecuteMessage(String executeMessage) {
this.executeMessage = executeMessage;
}
public String getExceptionInfo() {
return exceptionInfo;
}
public void setExceptionInfo(String exceptionInfo) {
this.exceptionInfo = exceptionInfo;
}
public Date getStartTime() {
return startTime;
}
public void setStartTime(Date startTime) {
this.startTime = startTime;
}
public Date getEndTime() {
return endTime;
}
public void setEndTime(Date endTime) {
this.endTime = endTime;
}
public Integer getExecuteDuration() {
return executeDuration;
}
public void setExecuteDuration(Integer executeDuration) {
this.executeDuration = executeDuration;
}
@Override
public String toString() {
return "TbCrontabLog{" +
"id=" + getID() +
", taskId='" + taskId + '\'' +
", taskName='" + taskName + '\'' +
", taskGroup='" + taskGroup + '\'' +
", executeStatus=" + executeStatus +
", startTime=" + startTime +
", endTime=" + endTime +
", executeDuration=" + executeDuration +
'}';
}
}

View File

@@ -0,0 +1,201 @@
package org.xyzh.common.dto.crontab;
import org.xyzh.common.dto.BaseDTO;
/**
* @description 定时任务配置表
* @filename TbCrontabTask.java
* @author yslg
* @copyright xyzh
* @since 2025-10-25
*/
public class TbCrontabTask extends BaseDTO {
private static final long serialVersionUID = 1L;
/**
* @description 任务ID
*/
private String taskId;
/**
* @description 任务名称
*/
private String taskName;
/**
* @description 任务分组
*/
private String taskGroup;
/**
* @description Bean名称
*/
private String beanName;
/**
* @description 方法名称
*/
private String methodName;
/**
* @description 方法参数
*/
private String methodParams;
/**
* @description Cron表达式
*/
private String cronExpression;
/**
* @description 任务状态0:暂停 1:运行中)
*/
private Integer status;
/**
* @description 任务描述
*/
private String description;
/**
* @description 是否允许并发执行0:否 1:是)
*/
private Integer concurrent;
/**
* @description 错过执行策略1:立即执行 2:执行一次 3:放弃执行)
*/
private Integer misfirePolicy;
/**
* @description 创建者
*/
private String creator;
/**
* @description 更新者
*/
private String updater;
public String getTaskId() {
return taskId;
}
public void setTaskId(String taskId) {
this.taskId = taskId;
}
public String getTaskName() {
return taskName;
}
public void setTaskName(String taskName) {
this.taskName = taskName;
}
public String getTaskGroup() {
return taskGroup;
}
public void setTaskGroup(String taskGroup) {
this.taskGroup = taskGroup;
}
public String getBeanName() {
return beanName;
}
public void setBeanName(String beanName) {
this.beanName = beanName;
}
public String getMethodName() {
return methodName;
}
public void setMethodName(String methodName) {
this.methodName = methodName;
}
public String getMethodParams() {
return methodParams;
}
public void setMethodParams(String methodParams) {
this.methodParams = methodParams;
}
public String getCronExpression() {
return cronExpression;
}
public void setCronExpression(String cronExpression) {
this.cronExpression = cronExpression;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Integer getConcurrent() {
return concurrent;
}
public void setConcurrent(Integer concurrent) {
this.concurrent = concurrent;
}
public Integer getMisfirePolicy() {
return misfirePolicy;
}
public void setMisfirePolicy(Integer misfirePolicy) {
this.misfirePolicy = misfirePolicy;
}
public String getCreator() {
return creator;
}
public void setCreator(String creator) {
this.creator = creator;
}
public String getUpdater() {
return updater;
}
public void setUpdater(String updater) {
this.updater = updater;
}
@Override
public String toString() {
return "TbCrontabTask{" +
"id=" + getID() +
", taskId='" + taskId + '\'' +
", taskName='" + taskName + '\'' +
", taskGroup='" + taskGroup + '\'' +
", beanName='" + beanName + '\'' +
", methodName='" + methodName + '\'' +
", cronExpression='" + cronExpression + '\'' +
", status=" + status +
", concurrent=" + concurrent +
", misfirePolicy=" + misfirePolicy +
'}';
}
}

View File

@@ -0,0 +1,212 @@
package org.xyzh.common.vo;
import java.io.Serializable;
import java.util.Date;
/**
* @description 定时任务日志视图对象
* @filename CrontabLogVO.java
* @author yslg
* @copyright xyzh
* @since 2025-10-25
*/
public class CrontabLogVO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* @description 日志ID
*/
private String id;
/**
* @description 任务ID
*/
private String taskId;
/**
* @description 任务名称
*/
private String taskName;
/**
* @description 任务分组
*/
private String taskGroup;
/**
* @description Bean名称
*/
private String beanName;
/**
* @description 方法名称
*/
private String methodName;
/**
* @description 方法参数
*/
private String methodParams;
/**
* @description 执行状态0:失败 1:成功)
*/
private Integer executeStatus;
/**
* @description 执行结果信息
*/
private String executeMessage;
/**
* @description 异常信息
*/
private String exceptionInfo;
/**
* @description 开始时间
*/
private Date startTime;
/**
* @description 结束时间
*/
private Date endTime;
/**
* @description 执行时长(毫秒)
*/
private Integer executeDuration;
/**
* @description 创建时间
*/
private Date createTime;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getTaskId() {
return taskId;
}
public void setTaskId(String taskId) {
this.taskId = taskId;
}
public String getTaskName() {
return taskName;
}
public void setTaskName(String taskName) {
this.taskName = taskName;
}
public String getTaskGroup() {
return taskGroup;
}
public void setTaskGroup(String taskGroup) {
this.taskGroup = taskGroup;
}
public String getBeanName() {
return beanName;
}
public void setBeanName(String beanName) {
this.beanName = beanName;
}
public String getMethodName() {
return methodName;
}
public void setMethodName(String methodName) {
this.methodName = methodName;
}
public String getMethodParams() {
return methodParams;
}
public void setMethodParams(String methodParams) {
this.methodParams = methodParams;
}
public Integer getExecuteStatus() {
return executeStatus;
}
public void setExecuteStatus(Integer executeStatus) {
this.executeStatus = executeStatus;
}
public String getExecuteMessage() {
return executeMessage;
}
public void setExecuteMessage(String executeMessage) {
this.executeMessage = executeMessage;
}
public String getExceptionInfo() {
return exceptionInfo;
}
public void setExceptionInfo(String exceptionInfo) {
this.exceptionInfo = exceptionInfo;
}
public Date getStartTime() {
return startTime;
}
public void setStartTime(Date startTime) {
this.startTime = startTime;
}
public Date getEndTime() {
return endTime;
}
public void setEndTime(Date endTime) {
this.endTime = endTime;
}
public Integer getExecuteDuration() {
return executeDuration;
}
public void setExecuteDuration(Integer executeDuration) {
this.executeDuration = executeDuration;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
@Override
public String toString() {
return "CrontabLogVO{" +
"id='" + id + '\'' +
", taskId='" + taskId + '\'' +
", taskName='" + taskName + '\'' +
", executeStatus=" + executeStatus +
", startTime=" + startTime +
", endTime=" + endTime +
", executeDuration=" + executeDuration +
'}';
}
}

View File

@@ -0,0 +1,238 @@
package org.xyzh.common.vo;
import java.io.Serializable;
import java.util.Date;
/**
* @description 定时任务视图对象
* @filename CrontabTaskVO.java
* @author yslg
* @copyright xyzh
* @since 2025-10-25
*/
public class CrontabTaskVO implements Serializable {
private static final long serialVersionUID = 1L;
/**
* @description 主键ID
*/
private String id;
/**
* @description 任务ID
*/
private String taskId;
/**
* @description 任务名称
*/
private String taskName;
/**
* @description 任务分组
*/
private String taskGroup;
/**
* @description Bean名称
*/
private String beanName;
/**
* @description 方法名称
*/
private String methodName;
/**
* @description 方法参数
*/
private String methodParams;
/**
* @description Cron表达式
*/
private String cronExpression;
/**
* @description 任务状态0:暂停 1:运行中)
*/
private Integer status;
/**
* @description 任务描述
*/
private String description;
/**
* @description 是否允许并发执行0:否 1:是)
*/
private Integer concurrent;
/**
* @description 错过执行策略1:立即执行 2:执行一次 3:放弃执行)
*/
private Integer misfirePolicy;
/**
* @description 下次执行时间
*/
private Date nextExecuteTime;
/**
* @description 创建者
*/
private String creator;
/**
* @description 创建时间
*/
private Date createTime;
/**
* @description 更新时间
*/
private Date updateTime;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getTaskId() {
return taskId;
}
public void setTaskId(String taskId) {
this.taskId = taskId;
}
public String getTaskName() {
return taskName;
}
public void setTaskName(String taskName) {
this.taskName = taskName;
}
public String getTaskGroup() {
return taskGroup;
}
public void setTaskGroup(String taskGroup) {
this.taskGroup = taskGroup;
}
public String getBeanName() {
return beanName;
}
public void setBeanName(String beanName) {
this.beanName = beanName;
}
public String getMethodName() {
return methodName;
}
public void setMethodName(String methodName) {
this.methodName = methodName;
}
public String getMethodParams() {
return methodParams;
}
public void setMethodParams(String methodParams) {
this.methodParams = methodParams;
}
public String getCronExpression() {
return cronExpression;
}
public void setCronExpression(String cronExpression) {
this.cronExpression = cronExpression;
}
public Integer getStatus() {
return status;
}
public void setStatus(Integer status) {
this.status = status;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public Integer getConcurrent() {
return concurrent;
}
public void setConcurrent(Integer concurrent) {
this.concurrent = concurrent;
}
public Integer getMisfirePolicy() {
return misfirePolicy;
}
public void setMisfirePolicy(Integer misfirePolicy) {
this.misfirePolicy = misfirePolicy;
}
public Date getNextExecuteTime() {
return nextExecuteTime;
}
public void setNextExecuteTime(Date nextExecuteTime) {
this.nextExecuteTime = nextExecuteTime;
}
public String getCreator() {
return creator;
}
public void setCreator(String creator) {
this.creator = creator;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
public Date getUpdateTime() {
return updateTime;
}
public void setUpdateTime(Date updateTime) {
this.updateTime = updateTime;
}
@Override
public String toString() {
return "CrontabTaskVO{" +
"id='" + id + '\'' +
", taskId='" + taskId + '\'' +
", taskName='" + taskName + '\'' +
", taskGroup='" + taskGroup + '\'' +
", cronExpression='" + cronExpression + '\'' +
", status=" + status +
", nextExecuteTime=" + nextExecuteTime +
'}';
}
}

View File

@@ -0,0 +1,248 @@
# 定时任务模块 (Crontab Module)
## 模块简介
定时任务模块是基于Spring Boot和Quartz框架实现的动态定时任务管理系统支持任务的动态创建、修改、启动、暂停和删除并提供完整的任务执行日志记录功能。
## 主要功能
### 1. 任务管理
- **创建任务**: 动态创建定时任务支持Cron表达式
- **更新任务**: 修改任务配置,自动重新调度
- **删除任务**: 删除任务并停止调度
- **启动/暂停**: 控制任务的运行状态
- **立即执行**: 手动触发任务执行一次
### 2. 任务配置
- **Bean名称和方法名**: 指定要执行的Spring Bean和方法
- **Cron表达式**: 灵活的时间调度配置
- **并发控制**: 支持控制任务是否允许并发执行
- **错过执行策略**: 配置任务错过执行时间后的处理策略
### 3. 日志记录
- **执行记录**: 记录每次任务执行的详细信息
- **执行时长**: 统计任务执行耗时
- **异常信息**: 详细记录执行失败的异常堆栈
- **日志清理**: 支持定期清理过期日志
## 数据库表结构
### tb_crontab_task (定时任务配置表)
```sql
- id: 主键ID
- task_id: 任务ID
- task_name: 任务名称
- task_group: 任务分组
- bean_name: Bean名称
- method_name: 方法名称
- method_params: 方法参数
- cron_expression: Cron表达式
- status: 任务状态(0:暂停 1:运行中)
- description: 任务描述
- concurrent: 是否允许并发执行
- misfire_policy: 错过执行策略
- creator: 创建者
- updater: 更新者
- create_time: 创建时间
- update_time: 更新时间
- delete_time: 删除时间
- deleted: 是否删除
```
### tb_crontab_log (定时任务执行日志表)
```sql
- id: 主键ID
- task_id: 任务ID
- task_name: 任务名称
- task_group: 任务分组
- bean_name: Bean名称
- method_name: 方法名称
- method_params: 方法参数
- execute_status: 执行状态(0:失败 1:成功)
- execute_message: 执行结果信息
- exception_info: 异常信息
- start_time: 开始时间
- end_time: 结束时间
- execute_duration: 执行时长(毫秒)
- create_time: 创建时间
- update_time: 更新时间
- delete_time: 删除时间
- deleted: 是否删除
```
## API接口
### 任务管理接口
#### 1. 创建定时任务
```
POST /crontab/task
Body: TbCrontabTask对象
```
#### 2. 更新定时任务
```
PUT /crontab/task
Body: TbCrontabTask对象
```
#### 3. 删除定时任务
```
DELETE /crontab/task
Body: TbCrontabTask对象
```
#### 4. 查询任务详情
```
GET /crontab/task/{taskId}
```
#### 5. 查询任务列表
```
POST /crontab/task/list
Body: 过滤条件
```
#### 6. 分页查询任务
```
POST /crontab/task/page
Body: PageRequest<TbCrontabTask>
```
#### 7. 启动任务
```
POST /crontab/task/start/{taskId}
```
#### 8. 暂停任务
```
POST /crontab/task/pause/{taskId}
```
#### 9. 立即执行任务
```
POST /crontab/task/execute/{taskId}
```
#### 10. 验证Cron表达式
```
GET /crontab/task/validate?cronExpression={expression}
```
### 日志管理接口
#### 1. 根据任务ID查询日志
```
GET /crontab/log/task/{taskId}
```
#### 2. 查询日志列表
```
POST /crontab/log/list
Body: 过滤条件
```
#### 3. 分页查询日志
```
POST /crontab/log/page
Body: PageRequest<TbCrontabLog>
```
#### 4. 查询日志详情
```
GET /crontab/log/{logId}
```
#### 5. 清理过期日志
```
DELETE /crontab/log/clean/{days}
```
#### 6. 删除日志
```
DELETE /crontab/log
Body: TbCrontabLog对象
```
## 示例任务
模块内置了三个示例任务:
### 1. SystemStatisticsTask (系统数据统计任务)
- Bean名称: `systemStatisticsTask`
- 方法名称: `execute`
- 功能: 执行系统数据统计
### 2. LogCleanTask (清理过期日志任务)
- Bean名称: `logCleanTask`
- 方法名称: `execute`
- 功能: 清理指定天数之前的日志
- 参数: 天数默认30天
### 3. DataBackupTask (数据备份任务)
- Bean名称: `dataBackupTask`
- 方法名称: `execute`
- 功能: 执行数据备份
- 参数: 备份类型full-全量incremental-增量)
## 自定义任务
要创建自定义任务,按照以下步骤:
1. 创建一个Spring Bean类
```java
@Component("myCustomTask")
public class MyCustomTask {
public void execute() {
// 任务执行逻辑
}
public void execute(String params) {
// 带参数的任务执行逻辑
}
}
```
2. 在数据库中添加任务配置
```sql
INSERT INTO tb_crontab_task (id, task_id, task_name, bean_name, method_name, cron_expression, status)
VALUES ('xxx', 'xxx', '我的自定义任务', 'myCustomTask', 'execute', '0 0 * * * ?', 1);
```
3. 或通过API接口创建任务
## Cron表达式示例
```
0 0 1 * * ? 每天凌晨1点执行
0 */5 * * * ? 每5分钟执行一次
0 0 0 1 * ? 每月1号凌晨执行
0 0 9-18 * * ? 每天9点到18点每小时执行
0 0 * * * ? 每小时执行
```
## 注意事项
1. **Cron表达式格式**: 使用Spring的Cron表达式格式6位或7位
2. **并发控制**: 设置`concurrent=0`可防止同一任务并发执行
3. **异常处理**: 任务执行失败会记录详细的异常信息到日志表
4. **任务初始化**: 系统启动时会自动加载所有状态为"运行中"的任务
5. **线程池配置**: 默认线程池大小为10可在`SchedulerConfig`中调整
## 技术栈
- Spring Boot 3.5.6
- Spring Scheduling
- MyBatis Plus 3.5.14
- MySQL 9.4.0
- Java 21
## 作者
yslg @ xyzh
## 更新日期
2025-10-25

View File

@@ -0,0 +1,90 @@
<?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">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.xyzh</groupId>
<artifactId>school-news</artifactId>
<version>${school-news.version}</version>
</parent>
<groupId>org.xyzh</groupId>
<artifactId>crontab</artifactId>
<version>${school-news.version}</version>
<properties>
<maven.compiler.source>21</maven.compiler.source>
<maven.compiler.target>21</maven.compiler.target>
</properties>
<dependencies>
<!-- API依赖 -->
<dependency>
<groupId>org.xyzh</groupId>
<artifactId>api-crontab</artifactId>
<version>${school-news.version}</version>
</dependency>
<!-- Common模块依赖 -->
<dependency>
<groupId>org.xyzh</groupId>
<artifactId>common-all</artifactId>
<version>${school-news.version}</version>
</dependency>
<!-- Spring Boot Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- MyBatis Plus for Spring Boot 3 -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
</dependency>
<!-- MySQL驱动 -->
<dependency>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
</dependency>
<!-- 数据库连接池 -->
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
</dependency>
<!-- Log4j2 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<!-- Quartz定时任务 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<parameters>true</parameters>
</configuration>
</plugin>
</plugins>
</build>
</project>

View File

@@ -0,0 +1,45 @@
package org.xyzh.crontab.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
/**
* @description 定时任务配置
* @filename SchedulerConfig.java
* @author yslg
* @copyright xyzh
* @since 2025-10-25
*/
@Configuration
@EnableScheduling
public class SchedulerConfig {
/**
* @description 配置任务调度线程池
* @return TaskScheduler
* @author yslg
* @since 2025-10-25
*/
@Bean
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
// 设置线程池大小
scheduler.setPoolSize(10);
// 设置线程名称前缀
scheduler.setThreadNamePrefix("crontab-task-");
// 设置是否等待所有任务完成后再关闭
scheduler.setWaitForTasksToCompleteOnShutdown(true);
// 设置等待时间(秒)
scheduler.setAwaitTerminationSeconds(60);
// 设置拒绝策略
scheduler.setRejectedExecutionHandler(new java.util.concurrent.ThreadPoolExecutor.CallerRunsPolicy());
// 初始化
scheduler.initialize();
return scheduler;
}
}

View File

@@ -0,0 +1,230 @@
package org.xyzh.crontab.controller;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import org.xyzh.api.crontab.CrontabService;
import org.xyzh.common.core.domain.ResultDomain;
import org.xyzh.common.core.page.PageParam;
import org.xyzh.common.core.page.PageRequest;
import org.xyzh.common.dto.crontab.TbCrontabTask;
import org.xyzh.common.dto.crontab.TbCrontabLog;
/**
* @description 定时任务控制器
* @filename CrontabController.java
* @author yslg
* @copyright xyzh
* @since 2025-10-25
*/
@RestController
@RequestMapping("/crontab")
public class CrontabController {
private static final Logger logger = LoggerFactory.getLogger(CrontabController.class);
@Autowired
private CrontabService crontabService;
// ----------------定时任务管理--------------------------------
/**
* @description 创建定时任务
* @param task 任务对象
* @return ResultDomain<TbCrontabTask>
* @author yslg
* @since 2025-10-25
*/
@PostMapping("/task")
public ResultDomain<TbCrontabTask> createTask(@RequestBody TbCrontabTask task) {
return crontabService.createTask(task);
}
/**
* @description 更新定时任务
* @param task 任务对象
* @return ResultDomain<TbCrontabTask>
* @author yslg
* @since 2025-10-25
*/
@PutMapping("/task")
public ResultDomain<TbCrontabTask> updateTask(@RequestBody TbCrontabTask task) {
return crontabService.updateTask(task);
}
/**
* @description 删除定时任务
* @param task 任务对象
* @return ResultDomain<TbCrontabTask>
* @author yslg
* @since 2025-10-25
*/
@DeleteMapping("/task")
public ResultDomain<TbCrontabTask> deleteTask(@RequestBody TbCrontabTask task) {
return crontabService.deleteTask(task.getID());
}
/**
* @description 根据ID查询任务
* @param taskId 任务ID
* @return ResultDomain<TbCrontabTask>
* @author yslg
* @since 2025-10-25
*/
@GetMapping("/task/{taskId}")
public ResultDomain<TbCrontabTask> getTaskById(@PathVariable String taskId) {
return crontabService.getTaskById(taskId);
}
/**
* @description 查询任务列表
* @param filter 过滤条件
* @return ResultDomain<TbCrontabTask>
* @author yslg
* @since 2025-10-25
*/
@PostMapping("/task/list")
public ResultDomain<TbCrontabTask> getTaskList(@RequestBody TbCrontabTask filter) {
return crontabService.getTaskList(filter);
}
/**
* @description 分页查询任务列表
* @param pageRequest 分页请求对象
* @return ResultDomain<TbCrontabTask>
* @author yslg
* @since 2025-10-25
*/
@PostMapping("/task/page")
public ResultDomain<TbCrontabTask> getTaskPage(@RequestBody PageRequest<TbCrontabTask> pageRequest) {
TbCrontabTask filter = pageRequest.getFilter();
PageParam pageParam = pageRequest.getPageParam();
return crontabService.getTaskPage(filter, pageParam);
}
/**
* @description 启动定时任务
* @param taskId 任务ID
* @return ResultDomain<TbCrontabTask>
* @author yslg
* @since 2025-10-25
*/
@PostMapping("/task/start/{taskId}")
public ResultDomain<TbCrontabTask> startTask(@PathVariable String taskId) {
return crontabService.startTask(taskId);
}
/**
* @description 暂停定时任务
* @param taskId 任务ID
* @return ResultDomain<TbCrontabTask>
* @author yslg
* @since 2025-10-25
*/
@PostMapping("/task/pause/{taskId}")
public ResultDomain<TbCrontabTask> pauseTask(@PathVariable String taskId) {
return crontabService.pauseTask(taskId);
}
/**
* @description 立即执行一次任务
* @param taskId 任务ID
* @return ResultDomain<TbCrontabTask>
* @author yslg
* @since 2025-10-25
*/
@PostMapping("/task/execute/{taskId}")
public ResultDomain<TbCrontabTask> executeTaskOnce(@PathVariable String taskId) {
return crontabService.executeTaskOnce(taskId);
}
/**
* @description 验证Cron表达式
* @param cronExpression Cron表达式
* @return ResultDomain<String>
* @author yslg
* @since 2025-10-25
*/
@GetMapping("/task/validate")
public ResultDomain<String> validateCronExpression(@RequestParam String cronExpression) {
return crontabService.validateCronExpression(cronExpression);
}
// ----------------定时任务日志--------------------------------
/**
* @description 根据任务ID查询日志
* @param taskId 任务ID
* @return ResultDomain<TbCrontabLog>
* @author yslg
* @since 2025-10-25
*/
@GetMapping("/log/task/{taskId}")
public ResultDomain<TbCrontabLog> getLogsByTaskId(@PathVariable String taskId) {
return crontabService.getLogsByTaskId(taskId);
}
/**
* @description 查询日志列表
* @param filter 过滤条件
* @return ResultDomain<TbCrontabLog>
* @author yslg
* @since 2025-10-25
*/
@PostMapping("/log/list")
public ResultDomain<TbCrontabLog> getLogList(@RequestBody TbCrontabLog filter) {
return crontabService.getLogList(filter);
}
/**
* @description 分页查询日志列表
* @param pageRequest 分页请求对象
* @return ResultDomain<TbCrontabLog>
* @author yslg
* @since 2025-10-25
*/
@PostMapping("/log/page")
public ResultDomain<TbCrontabLog> getLogPage(@RequestBody PageRequest<TbCrontabLog> pageRequest) {
TbCrontabLog filter = pageRequest.getFilter();
PageParam pageParam = pageRequest.getPageParam();
return crontabService.getLogPage(filter, pageParam);
}
/**
* @description 根据ID查询日志详情
* @param logId 日志ID
* @return ResultDomain<TbCrontabLog>
* @author yslg
* @since 2025-10-25
*/
@GetMapping("/log/{logId}")
public ResultDomain<TbCrontabLog> getLogById(@PathVariable String logId) {
return crontabService.getLogById(logId);
}
/**
* @description 清理指定天数之前的日志
* @param days 天数
* @return ResultDomain<Integer>
* @author yslg
* @since 2025-10-25
*/
@DeleteMapping("/log/clean/{days}")
public ResultDomain<Integer> cleanLogs(@PathVariable Integer days) {
return crontabService.cleanLogs(days);
}
/**
* @description 删除日志
* @param log 日志对象
* @return ResultDomain<TbCrontabLog>
* @author yslg
* @since 2025-10-25
*/
@DeleteMapping("/log")
public ResultDomain<TbCrontabLog> deleteLog(@RequestBody TbCrontabLog log) {
return crontabService.deleteLog(log.getID());
}
}

View File

@@ -0,0 +1,124 @@
package org.xyzh.crontab.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.xyzh.common.core.page.PageParam;
import org.xyzh.common.dto.crontab.TbCrontabLog;
import java.util.Date;
import java.util.List;
/**
* @description 定时任务日志数据访问层
* @filename CrontabLogMapper.java
* @author yslg
* @copyright xyzh
* @since 2025-10-25
*/
@Mapper
public interface CrontabLogMapper extends BaseMapper<TbCrontabLog> {
/**
* @description 插入日志
* @param log 日志信息
* @return int 影响行数
* @author yslg
* @since 2025-10-25
*/
int insertLog(@Param("log") TbCrontabLog log);
/**
* @description 根据ID查询日志
* @param logId 日志ID
* @return TbCrontabLog 日志信息
* @author yslg
* @since 2025-10-25
*/
TbCrontabLog selectLogById(@Param("logId") String logId);
/**
* @description 根据任务ID查询日志列表
* @param taskId 任务ID
* @return List<TbCrontabLog> 日志列表
* @author yslg
* @since 2025-10-25
*/
List<TbCrontabLog> selectLogsByTaskId(@Param("taskId") String taskId);
/**
* @description 根据过滤条件查询日志列表
* @param filter 过滤条件
* @return List<TbCrontabLog> 日志列表
* @author yslg
* @since 2025-10-25
*/
List<TbCrontabLog> selectLogList(@Param("filter") TbCrontabLog filter);
/**
* @description 分页查询日志列表
* @param filter 过滤条件
* @param pageParam 分页参数
* @return List<TbCrontabLog> 日志列表
* @author yslg
* @since 2025-10-25
*/
List<TbCrontabLog> selectLogPage(@Param("filter") TbCrontabLog filter, @Param("pageParam") PageParam pageParam);
/**
* @description 统计日志总数
* @param filter 过滤条件
* @return long 总数
* @author yslg
* @since 2025-10-25
*/
long countLogs(@Param("filter") TbCrontabLog filter);
/**
* @description 删除日志(逻辑删除)
* @param logId 日志ID
* @return int 影响行数
* @author yslg
* @since 2025-10-25
*/
int deleteLog(@Param("logId") String logId);
/**
* @description 批量删除日志
* @param ids 日志ID列表
* @return int 影响行数
* @author yslg
* @since 2025-10-25
*/
int batchDeleteLogs(@Param("ids") List<String> ids);
/**
* @description 清理指定时间之前的日志
* @param beforeDate 指定时间
* @return int 影响行数
* @author yslg
* @since 2025-10-25
*/
int cleanLogsByDate(@Param("beforeDate") Date beforeDate);
/**
* @description 根据任务ID和执行状态查询日志
* @param taskId 任务ID
* @param executeStatus 执行状态
* @return List<TbCrontabLog> 日志列表
* @author yslg
* @since 2025-10-25
*/
List<TbCrontabLog> selectLogsByTaskIdAndStatus(@Param("taskId") String taskId, @Param("executeStatus") Integer executeStatus);
/**
* @description 查询最近的执行日志
* @param taskId 任务ID
* @param limit 限制数量
* @return List<TbCrontabLog> 日志列表
* @author yslg
* @since 2025-10-25
*/
List<TbCrontabLog> selectRecentLogs(@Param("taskId") String taskId, @Param("limit") Integer limit);
}

View File

@@ -0,0 +1,104 @@
package org.xyzh.crontab.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.xyzh.common.core.page.PageParam;
import org.xyzh.common.dto.crontab.TbCrontabTask;
import java.util.List;
/**
* @description 定时任务数据访问层
* @filename CrontabTaskMapper.java
* @author yslg
* @copyright xyzh
* @since 2025-10-25
*/
@Mapper
public interface CrontabTaskMapper extends BaseMapper<TbCrontabTask> {
/**
* @description 插入任务
* @param task 任务信息
* @return int 影响行数
* @author yslg
* @since 2025-10-25
*/
int insertTask(@Param("task") TbCrontabTask task);
/**
* @description 更新任务
* @param task 任务信息
* @return int 影响行数
* @author yslg
* @since 2025-10-25
*/
int updateTask(@Param("task") TbCrontabTask task);
/**
* @description 删除任务(逻辑删除)
* @param taskId 任务ID
* @return int 影响行数
* @author yslg
* @since 2025-10-25
*/
int deleteTask(@Param("taskId") String taskId);
/**
* @description 根据ID查询任务
* @param taskId 任务ID
* @return TbCrontabTask 任务信息
* @author yslg
* @since 2025-10-25
*/
TbCrontabTask selectTaskById(@Param("taskId") String taskId);
/**
* @description 根据过滤条件查询任务列表
* @param filter 过滤条件
* @return List<TbCrontabTask> 任务列表
* @author yslg
* @since 2025-10-25
*/
List<TbCrontabTask> selectTaskList(@Param("filter") TbCrontabTask filter);
/**
* @description 分页查询任务列表
* @param filter 过滤条件
* @param pageParam 分页参数
* @return List<TbCrontabTask> 任务列表
* @author yslg
* @since 2025-10-25
*/
List<TbCrontabTask> selectTaskPage(@Param("filter") TbCrontabTask filter, @Param("pageParam") PageParam pageParam);
/**
* @description 查询所有运行中的任务
* @return List<TbCrontabTask> 任务列表
* @author yslg
* @since 2025-10-25
*/
List<TbCrontabTask> selectRunningTasks();
/**
* @description 更新任务状态
* @param taskId 任务ID
* @param status 状态
* @return int 影响行数
* @author yslg
* @since 2025-10-25
*/
int updateTaskStatus(@Param("taskId") String taskId, @Param("status") Integer status);
/**
* @description 根据Bean名称和方法名称查询任务
* @param beanName Bean名称
* @param methodName 方法名称
* @return TbCrontabTask 任务信息
* @author yslg
* @since 2025-10-25
*/
TbCrontabTask selectTaskByBeanAndMethod(@Param("beanName") String beanName, @Param("methodName") String methodName);
}

View File

@@ -0,0 +1,57 @@
package org.xyzh.crontab.scheduler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import org.xyzh.common.dto.crontab.TbCrontabTask;
import org.xyzh.crontab.mapper.CrontabTaskMapper;
import java.util.List;
/**
* @description 定时任务初始化器,启动时加载所有运行中的任务
* @filename SchedulerInitializer.java
* @author yslg
* @copyright xyzh
* @since 2025-10-25
*/
@Component
public class SchedulerInitializer implements CommandLineRunner {
private static final Logger logger = LoggerFactory.getLogger(SchedulerInitializer.class);
@Autowired
private CrontabTaskMapper taskMapper;
@Autowired
private SchedulerManager schedulerManager;
@Override
public void run(String... args) throws Exception {
try {
logger.info("开始初始化定时任务...");
// 查询所有运行中的任务
List<TbCrontabTask> runningTasks = taskMapper.selectRunningTasks();
if (runningTasks != null && !runningTasks.isEmpty()) {
for (TbCrontabTask task : runningTasks) {
try {
schedulerManager.scheduleTask(task);
logger.info("加载定时任务: {} [{}]", task.getTaskName(), task.getCronExpression());
} catch (Exception e) {
logger.error("加载定时任务失败: {}", task.getTaskName(), e);
}
}
logger.info("定时任务初始化完成,共加载 {} 个任务", runningTasks.size());
} else {
logger.info("没有需要加载的定时任务");
}
} catch (Exception e) {
logger.error("定时任务初始化异常: ", e);
}
}
}

View File

@@ -0,0 +1,157 @@
package org.xyzh.crontab.scheduler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.support.CronTrigger;
import org.springframework.stereotype.Component;
import org.xyzh.common.dto.crontab.TbCrontabTask;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;
/**
* @description 定时任务调度管理器
* @filename SchedulerManager.java
* @author yslg
* @copyright xyzh
* @since 2025-10-25
*/
@Component
public class SchedulerManager {
private static final Logger logger = LoggerFactory.getLogger(SchedulerManager.class);
@Autowired
private TaskScheduler taskScheduler;
@Autowired
private ApplicationContext applicationContext;
@Autowired
private TaskExecutor taskExecutor;
/**
* 存储已调度的任务
* Key: 任务ID, Value: ScheduledFuture
*/
private final Map<String, ScheduledFuture<?>> scheduledTasks = new ConcurrentHashMap<>();
/**
* @description 调度任务
* @param task 任务对象
* @author yslg
* @since 2025-10-25
*/
public void scheduleTask(TbCrontabTask task) {
try {
String taskId = task.getID();
// 如果任务已经在调度中,先取消
if (scheduledTasks.containsKey(taskId)) {
unscheduleTask(task);
}
// 创建Cron触发器
CronTrigger cronTrigger = new CronTrigger(task.getCronExpression());
// 调度任务
ScheduledFuture<?> future = taskScheduler.schedule(
() -> taskExecutor.executeTask(task),
cronTrigger
);
// 保存调度信息
scheduledTasks.put(taskId, future);
logger.info("任务调度成功: {} [{}]", task.getTaskName(), task.getCronExpression());
} catch (Exception e) {
logger.error("任务调度失败: {}", task.getTaskName(), e);
}
}
/**
* @description 取消任务调度
* @param task 任务对象
* @author yslg
* @since 2025-10-25
*/
public void unscheduleTask(TbCrontabTask task) {
try {
String taskId = task.getID();
ScheduledFuture<?> future = scheduledTasks.get(taskId);
if (future != null) {
future.cancel(false);
scheduledTasks.remove(taskId);
logger.info("任务取消调度成功: {}", task.getTaskName());
}
} catch (Exception e) {
logger.error("任务取消调度失败: {}", task.getTaskName(), e);
}
}
/**
* @description 重新调度任务
* @param task 任务对象
* @author yslg
* @since 2025-10-25
*/
public void rescheduleTask(TbCrontabTask task) {
unscheduleTask(task);
scheduleTask(task);
}
/**
* @description 立即执行一次任务
* @param task 任务对象
* @author yslg
* @since 2025-10-25
*/
public void executeTaskOnce(TbCrontabTask task) {
try {
taskExecutor.executeTask(task);
logger.info("立即执行任务: {}", task.getTaskName());
} catch (Exception e) {
logger.error("立即执行任务失败: {}", task.getTaskName(), e);
}
}
/**
* @description 检查任务是否在调度中
* @param taskId 任务ID
* @return boolean
* @author yslg
* @since 2025-10-25
*/
public boolean isTaskScheduled(String taskId) {
return scheduledTasks.containsKey(taskId);
}
/**
* @description 获取所有调度中的任务数量
* @return int
* @author yslg
* @since 2025-10-25
*/
public int getScheduledTaskCount() {
return scheduledTasks.size();
}
/**
* @description 清除所有任务调度
* @author yslg
* @since 2025-10-25
*/
public void clearAllSchedules() {
scheduledTasks.forEach((taskId, future) -> {
future.cancel(false);
});
scheduledTasks.clear();
logger.info("已清除所有任务调度");
}
}

View File

@@ -0,0 +1,128 @@
package org.xyzh.crontab.scheduler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import org.xyzh.common.dto.crontab.TbCrontabLog;
import org.xyzh.common.dto.crontab.TbCrontabTask;
import org.xyzh.common.utils.IDUtils;
import org.xyzh.crontab.mapper.CrontabLogMapper;
import java.lang.reflect.Method;
import java.util.Date;
/**
* @description 任务执行器
* @filename TaskExecutor.java
* @author yslg
* @copyright xyzh
* @since 2025-10-25
*/
@Component
public class TaskExecutor {
private static final Logger logger = LoggerFactory.getLogger(TaskExecutor.class);
@Autowired
private ApplicationContext applicationContext;
@Autowired
private CrontabLogMapper logMapper;
/**
* @description 执行任务
* @param task 任务对象
* @author yslg
* @since 2025-10-25
*/
public void executeTask(TbCrontabTask task) {
Date startTime = new Date();
TbCrontabLog log = new TbCrontabLog();
log.setID(IDUtils.generateID());
log.setTaskId(task.getTaskId());
log.setTaskName(task.getTaskName());
log.setTaskGroup(task.getTaskGroup());
log.setBeanName(task.getBeanName());
log.setMethodName(task.getMethodName());
log.setMethodParams(task.getMethodParams());
log.setStartTime(startTime);
log.setCreateTime(new Date());
log.setDeleted(false);
try {
// 检查是否允许并发执行
if (task.getConcurrent() == 0) {
// TODO: 可以添加分布式锁来防止并发执行
}
// 获取Bean实例
Object bean = applicationContext.getBean(task.getBeanName());
// 获取方法
Method method;
if (task.getMethodParams() != null && !task.getMethodParams().isEmpty()) {
// 如果有参数,需要解析参数类型
method = bean.getClass().getMethod(task.getMethodName(), String.class);
method.invoke(bean, task.getMethodParams());
} else {
// 无参方法
method = bean.getClass().getMethod(task.getMethodName());
method.invoke(bean);
}
// 执行成功
Date endTime = new Date();
log.setEndTime(endTime);
log.setExecuteDuration((int) (endTime.getTime() - startTime.getTime()));
log.setExecuteStatus(1);
log.setExecuteMessage("执行成功");
logger.info("任务执行成功: {} [{}ms]", task.getTaskName(), log.getExecuteDuration());
} catch (Exception e) {
// 执行失败
Date endTime = new Date();
log.setEndTime(endTime);
log.setExecuteDuration((int) (endTime.getTime() - startTime.getTime()));
log.setExecuteStatus(0);
log.setExecuteMessage("执行失败");
log.setExceptionInfo(getExceptionStackTrace(e));
logger.error("任务执行失败: {}", task.getTaskName(), e);
} finally {
// 保存执行日志
try {
logMapper.insertLog(log);
} catch (Exception e) {
logger.error("保存任务执行日志失败: {}", task.getTaskName(), e);
}
}
}
/**
* @description 获取异常堆栈信息
* @param e 异常
* @return String
* @author yslg
* @since 2025-10-25
*/
private String getExceptionStackTrace(Exception e) {
StringBuilder sb = new StringBuilder();
sb.append(e.getClass().getName()).append(": ").append(e.getMessage()).append("\n");
StackTraceElement[] stackTrace = e.getStackTrace();
int limit = Math.min(stackTrace.length, 10); // 只保留前10行
for (int i = 0; i < limit; i++) {
sb.append("\tat ").append(stackTrace[i].toString()).append("\n");
}
if (stackTrace.length > limit) {
sb.append("\t... ").append(stackTrace.length - limit).append(" more\n");
}
return sb.toString();
}
}

View File

@@ -0,0 +1,494 @@
package org.xyzh.crontab.service.impl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.support.CronExpression;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.xyzh.api.crontab.CrontabService;
import org.xyzh.common.core.domain.ResultDomain;
import org.xyzh.common.core.page.PageParam;
import org.xyzh.common.dto.crontab.TbCrontabTask;
import org.xyzh.common.dto.crontab.TbCrontabLog;
import org.xyzh.common.utils.IDUtils;
import org.xyzh.crontab.mapper.CrontabTaskMapper;
import org.xyzh.crontab.mapper.CrontabLogMapper;
import org.xyzh.crontab.scheduler.SchedulerManager;
import java.time.LocalDateTime;
import java.util.Calendar;
import java.util.Date;
import java.util.List;
/**
* @description 定时任务服务实现类
* @filename CrontabServiceImpl.java
* @author yslg
* @copyright xyzh
* @since 2025-10-25
*/
@Service
public class CrontabServiceImpl implements CrontabService {
private static final Logger logger = LoggerFactory.getLogger(CrontabServiceImpl.class);
@Autowired
private CrontabTaskMapper taskMapper;
@Autowired
private CrontabLogMapper logMapper;
@Autowired
private SchedulerManager schedulerManager;
// ----------------定时任务管理--------------------------------
@Override
@Transactional(rollbackFor = Exception.class)
public ResultDomain<TbCrontabTask> createTask(TbCrontabTask task) {
ResultDomain<TbCrontabTask> resultDomain = new ResultDomain<>();
try {
// 验证Cron表达式
if (!CronExpression.isValidExpression(task.getCronExpression())) {
resultDomain.fail("Cron表达式格式不正确");
return resultDomain;
}
// 生成ID
task.setID(IDUtils.generateID());
task.setTaskId(IDUtils.generateID());
task.setCreateTime(new Date());
task.setDeleted(false);
// 默认值
if (task.getStatus() == null) {
task.setStatus(0); // 默认暂停
}
if (task.getConcurrent() == null) {
task.setConcurrent(0); // 默认不允许并发
}
if (task.getMisfirePolicy() == null) {
task.setMisfirePolicy(1); // 默认立即执行
}
int result = taskMapper.insertTask(task);
if (result > 0) {
logger.info("创建定时任务成功: {}", task.getTaskName());
// 如果任务状态为启动,则立即调度
if (task.getStatus() == 1) {
schedulerManager.scheduleTask(task);
}
resultDomain.success("创建定时任务成功", task);
} else {
resultDomain.fail("创建定时任务失败");
}
} catch (Exception e) {
logger.error("创建定时任务异常: ", e);
resultDomain.fail("创建定时任务异常: " + e.getMessage());
}
return resultDomain;
}
@Override
@Transactional(rollbackFor = Exception.class)
public ResultDomain<TbCrontabTask> updateTask(TbCrontabTask task) {
ResultDomain<TbCrontabTask> resultDomain = new ResultDomain<>();
try {
if (task.getID() == null) {
resultDomain.fail("任务ID不能为空");
return resultDomain;
}
// 验证Cron表达式
if (task.getCronExpression() != null && !CronExpression.isValidExpression(task.getCronExpression())) {
resultDomain.fail("Cron表达式格式不正确");
return resultDomain;
}
task.setUpdateTime(new Date());
int result = taskMapper.updateTask(task);
if (result > 0) {
logger.info("更新定时任务成功: {}", task.getTaskName());
// 重新调度任务
TbCrontabTask dbTask = taskMapper.selectTaskById(task.getID());
if (dbTask != null && dbTask.getStatus() == 1) {
schedulerManager.rescheduleTask(dbTask);
}
resultDomain.success("更新定时任务成功", task);
} else {
resultDomain.fail("更新定时任务失败");
}
} catch (Exception e) {
logger.error("更新定时任务异常: ", e);
resultDomain.fail("更新定时任务异常: " + e.getMessage());
}
return resultDomain;
}
@Override
@Transactional(rollbackFor = Exception.class)
public ResultDomain<TbCrontabTask> deleteTask(String taskId) {
ResultDomain<TbCrontabTask> resultDomain = new ResultDomain<>();
try {
if (taskId == null || taskId.isEmpty()) {
resultDomain.fail("任务ID不能为空");
return resultDomain;
}
// 先停止任务调度
TbCrontabTask task = taskMapper.selectTaskById(taskId);
if (task != null && task.getStatus() == 1) {
schedulerManager.unscheduleTask(task);
}
int result = taskMapper.deleteTask(taskId);
if (result > 0) {
logger.info("删除定时任务成功任务ID: {}", taskId);
resultDomain.success("删除定时任务成功", (TbCrontabTask) null);
} else {
resultDomain.fail("删除定时任务失败");
}
} catch (Exception e) {
logger.error("删除定时任务异常: ", e);
resultDomain.fail("删除定时任务异常: " + e.getMessage());
}
return resultDomain;
}
@Override
public ResultDomain<TbCrontabTask> getTaskById(String taskId) {
ResultDomain<TbCrontabTask> resultDomain = new ResultDomain<>();
try {
if (taskId == null || taskId.isEmpty()) {
resultDomain.fail("任务ID不能为空");
return resultDomain;
}
TbCrontabTask task = taskMapper.selectTaskById(taskId);
if (task != null) {
resultDomain.success("查询成功", task);
} else {
resultDomain.fail("任务不存在");
}
} catch (Exception e) {
logger.error("查询定时任务异常: ", e);
resultDomain.fail("查询定时任务异常: " + e.getMessage());
}
return resultDomain;
}
@Override
public ResultDomain<TbCrontabTask> getTaskList(TbCrontabTask filter) {
ResultDomain<TbCrontabTask> resultDomain = new ResultDomain<>();
try {
if (filter == null) {
filter = new TbCrontabTask();
}
filter.setDeleted(false);
List<TbCrontabTask> list = taskMapper.selectTaskList(filter);
resultDomain.success("查询成功", list);
} catch (Exception e) {
logger.error("查询定时任务列表异常: ", e);
resultDomain.fail("查询定时任务列表异常: " + e.getMessage());
}
return resultDomain;
}
@Override
public ResultDomain<TbCrontabTask> getTaskPage(TbCrontabTask filter, PageParam pageParam) {
ResultDomain<TbCrontabTask> resultDomain = new ResultDomain<>();
try {
if (filter == null) {
filter = new TbCrontabTask();
}
filter.setDeleted(false);
if (pageParam == null) {
pageParam = new PageParam();
}
List<TbCrontabTask> list = taskMapper.selectTaskPage(filter, pageParam);
resultDomain.success("查询成功", list);
} catch (Exception e) {
logger.error("分页查询定时任务异常: ", e);
resultDomain.fail("分页查询定时任务异常: " + e.getMessage());
}
return resultDomain;
}
@Override
@Transactional(rollbackFor = Exception.class)
public ResultDomain<TbCrontabTask> startTask(String taskId) {
ResultDomain<TbCrontabTask> resultDomain = new ResultDomain<>();
try {
if (taskId == null || taskId.isEmpty()) {
resultDomain.fail("任务ID不能为空");
return resultDomain;
}
TbCrontabTask task = taskMapper.selectTaskById(taskId);
if (task == null) {
resultDomain.fail("任务不存在");
return resultDomain;
}
if (task.getStatus() == 1) {
resultDomain.fail("任务已在运行中");
return resultDomain;
}
// 更新状态为运行中
int result = taskMapper.updateTaskStatus(taskId, 1);
if (result > 0) {
task.setStatus(1);
// 调度任务
schedulerManager.scheduleTask(task);
logger.info("启动定时任务成功: {}", task.getTaskName());
resultDomain.success("启动定时任务成功", task);
} else {
resultDomain.fail("启动定时任务失败");
}
} catch (Exception e) {
logger.error("启动定时任务异常: ", e);
resultDomain.fail("启动定时任务异常: " + e.getMessage());
}
return resultDomain;
}
@Override
@Transactional(rollbackFor = Exception.class)
public ResultDomain<TbCrontabTask> pauseTask(String taskId) {
ResultDomain<TbCrontabTask> resultDomain = new ResultDomain<>();
try {
if (taskId == null || taskId.isEmpty()) {
resultDomain.fail("任务ID不能为空");
return resultDomain;
}
TbCrontabTask task = taskMapper.selectTaskById(taskId);
if (task == null) {
resultDomain.fail("任务不存在");
return resultDomain;
}
if (task.getStatus() == 0) {
resultDomain.fail("任务已暂停");
return resultDomain;
}
// 取消调度
schedulerManager.unscheduleTask(task);
// 更新状态为暂停
int result = taskMapper.updateTaskStatus(taskId, 0);
if (result > 0) {
task.setStatus(0);
logger.info("暂停定时任务成功: {}", task.getTaskName());
resultDomain.success("暂停定时任务成功", task);
} else {
resultDomain.fail("暂停定时任务失败");
}
} catch (Exception e) {
logger.error("暂停定时任务异常: ", e);
resultDomain.fail("暂停定时任务异常: " + e.getMessage());
}
return resultDomain;
}
@Override
public ResultDomain<TbCrontabTask> executeTaskOnce(String taskId) {
ResultDomain<TbCrontabTask> resultDomain = new ResultDomain<>();
try {
if (taskId == null || taskId.isEmpty()) {
resultDomain.fail("任务ID不能为空");
return resultDomain;
}
TbCrontabTask task = taskMapper.selectTaskById(taskId);
if (task == null) {
resultDomain.fail("任务不存在");
return resultDomain;
}
// 立即执行一次
schedulerManager.executeTaskOnce(task);
logger.info("立即执行定时任务: {}", task.getTaskName());
resultDomain.success("立即执行定时任务成功", task);
} catch (Exception e) {
logger.error("立即执行定时任务异常: ", e);
resultDomain.fail("立即执行定时任务异常: " + e.getMessage());
}
return resultDomain;
}
@Override
public ResultDomain<String> validateCronExpression(String cronExpression) {
ResultDomain<String> resultDomain = new ResultDomain<>();
try {
if (cronExpression == null || cronExpression.isEmpty()) {
resultDomain.fail("Cron表达式不能为空");
return resultDomain;
}
if (!CronExpression.isValidExpression(cronExpression)) {
resultDomain.fail("Cron表达式格式不正确");
return resultDomain;
}
// 计算下次执行时间
CronExpression cron = CronExpression.parse(cronExpression);
LocalDateTime nextExecution = cron.next(LocalDateTime.now());
if (nextExecution != null) {
String message = "Cron表达式有效下次执行时间: " + nextExecution;
resultDomain.success("验证成功", message);
} else {
resultDomain.fail("无法计算下次执行时间");
}
} catch (Exception e) {
logger.error("验证Cron表达式异常: ", e);
resultDomain.fail("验证Cron表达式异常: " + e.getMessage());
}
return resultDomain;
}
// ----------------定时任务日志--------------------------------
@Override
public ResultDomain<TbCrontabLog> getLogsByTaskId(String taskId) {
ResultDomain<TbCrontabLog> resultDomain = new ResultDomain<>();
try {
if (taskId == null || taskId.isEmpty()) {
resultDomain.fail("任务ID不能为空");
return resultDomain;
}
List<TbCrontabLog> list = logMapper.selectLogsByTaskId(taskId);
resultDomain.success("查询成功", list);
} catch (Exception e) {
logger.error("查询任务日志异常: ", e);
resultDomain.fail("查询任务日志异常: " + e.getMessage());
}
return resultDomain;
}
@Override
public ResultDomain<TbCrontabLog> getLogList(TbCrontabLog filter) {
ResultDomain<TbCrontabLog> resultDomain = new ResultDomain<>();
try {
if (filter == null) {
filter = new TbCrontabLog();
}
filter.setDeleted(false);
List<TbCrontabLog> list = logMapper.selectLogList(filter);
resultDomain.success("查询成功", list);
} catch (Exception e) {
logger.error("查询日志列表异常: ", e);
resultDomain.fail("查询日志列表异常: " + e.getMessage());
}
return resultDomain;
}
@Override
public ResultDomain<TbCrontabLog> getLogPage(TbCrontabLog filter, PageParam pageParam) {
ResultDomain<TbCrontabLog> resultDomain = new ResultDomain<>();
try {
if (filter == null) {
filter = new TbCrontabLog();
}
filter.setDeleted(false);
if (pageParam == null) {
pageParam = new PageParam();
}
List<TbCrontabLog> list = logMapper.selectLogPage(filter, pageParam);
resultDomain.success("查询成功", list);
} catch (Exception e) {
logger.error("分页查询日志异常: ", e);
resultDomain.fail("分页查询日志异常: " + e.getMessage());
}
return resultDomain;
}
@Override
public ResultDomain<TbCrontabLog> getLogById(String logId) {
ResultDomain<TbCrontabLog> resultDomain = new ResultDomain<>();
try {
if (logId == null || logId.isEmpty()) {
resultDomain.fail("日志ID不能为空");
return resultDomain;
}
TbCrontabLog log = logMapper.selectLogById(logId);
if (log != null) {
resultDomain.success("查询成功", log);
} else {
resultDomain.fail("日志不存在");
}
} catch (Exception e) {
logger.error("查询日志详情异常: ", e);
resultDomain.fail("查询日志详情异常: " + e.getMessage());
}
return resultDomain;
}
@Override
@Transactional(rollbackFor = Exception.class)
public ResultDomain<Integer> cleanLogs(Integer days) {
ResultDomain<Integer> resultDomain = new ResultDomain<>();
try {
if (days == null || days <= 0) {
resultDomain.fail("天数参数无效");
return resultDomain;
}
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DAY_OF_MONTH, -days);
Date beforeDate = calendar.getTime();
int count = logMapper.cleanLogsByDate(beforeDate);
logger.info("清理{}天前的日志,共{}条", days, count);
resultDomain.success("清理成功", count);
} catch (Exception e) {
logger.error("清理日志异常: ", e);
resultDomain.fail("清理日志异常: " + e.getMessage());
}
return resultDomain;
}
@Override
@Transactional(rollbackFor = Exception.class)
public ResultDomain<TbCrontabLog> deleteLog(String logId) {
ResultDomain<TbCrontabLog> resultDomain = new ResultDomain<>();
try {
if (logId == null || logId.isEmpty()) {
resultDomain.fail("日志ID不能为空");
return resultDomain;
}
int result = logMapper.deleteLog(logId);
if (result > 0) {
logger.info("删除日志成功日志ID: {}", logId);
resultDomain.success("删除日志成功", (TbCrontabLog) null);
} else {
resultDomain.fail("删除日志失败");
}
} catch (Exception e) {
logger.error("删除日志异常: ", e);
resultDomain.fail("删除日志异常: " + e.getMessage());
}
return resultDomain;
}
}

View File

@@ -0,0 +1,60 @@
package org.xyzh.crontab.task;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.text.SimpleDateFormat;
import java.util.Date;
/**
* @description 数据备份任务
* @filename DataBackupTask.java
* @author yslg
* @copyright xyzh
* @since 2025-10-25
*/
@Component("dataBackupTask")
public class DataBackupTask {
private static final Logger logger = LoggerFactory.getLogger(DataBackupTask.class);
/**
* @description 执行数据备份
* @author yslg
* @since 2025-10-25
*/
public void execute() {
logger.info("开始执行数据备份任务...");
try {
SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMdd_HHmmss");
String backupTime = sdf.format(new Date());
// TODO: 实现数据备份逻辑
// 1. 备份数据库
// 2. 备份文件
// 3. 压缩备份文件
// 4. 上传到备份服务器或云存储
Thread.sleep(2000); // 模拟执行
logger.info("数据备份任务执行完成,备份标识: {}", backupTime);
} catch (Exception e) {
logger.error("数据备份任务执行失败: ", e);
throw new RuntimeException("数据备份任务执行失败", e);
}
}
/**
* @description 执行带参数的备份任务
* @param params 参数备份类型full-全量incremental-增量)
* @author yslg
* @since 2025-10-25
*/
public void execute(String params) {
logger.info("开始执行数据备份任务,备份类型: {}", params);
execute();
}
}

View File

@@ -0,0 +1,68 @@
package org.xyzh.crontab.task;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.xyzh.crontab.mapper.CrontabLogMapper;
import java.util.Calendar;
import java.util.Date;
/**
* @description 清理过期日志任务
* @filename LogCleanTask.java
* @author yslg
* @copyright xyzh
* @since 2025-10-25
*/
@Component("logCleanTask")
public class LogCleanTask {
private static final Logger logger = LoggerFactory.getLogger(LogCleanTask.class);
@Autowired
private CrontabLogMapper logMapper;
/**
* @description 执行日志清理默认清理30天前的日志
* @author yslg
* @since 2025-10-25
*/
public void execute() {
execute("30");
}
/**
* @description 执行日志清理
* @param params 天数参数
* @author yslg
* @since 2025-10-25
*/
public void execute(String params) {
logger.info("开始执行日志清理任务...");
try {
int days = 30; // 默认30天
if (params != null && !params.isEmpty()) {
try {
days = Integer.parseInt(params);
} catch (NumberFormatException e) {
logger.warn("参数格式错误使用默认值30天");
}
}
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.DAY_OF_MONTH, -days);
Date beforeDate = calendar.getTime();
int count = logMapper.cleanLogsByDate(beforeDate);
logger.info("日志清理任务执行完成,共清理{}条日志", count);
} catch (Exception e) {
logger.error("日志清理任务执行失败: ", e);
throw new RuntimeException("日志清理任务执行失败", e);
}
}
}

View File

@@ -0,0 +1,54 @@
package org.xyzh.crontab.task;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
/**
* @description 系统数据统计任务
* @filename SystemStatisticsTask.java
* @author yslg
* @copyright xyzh
* @since 2025-10-25
*/
@Component("systemStatisticsTask")
public class SystemStatisticsTask {
private static final Logger logger = LoggerFactory.getLogger(SystemStatisticsTask.class);
/**
* @description 执行系统数据统计
* @author yslg
* @since 2025-10-25
*/
public void execute() {
logger.info("开始执行系统数据统计任务...");
try {
// TODO: 实现系统数据统计逻辑
// 1. 统计用户数据
// 2. 统计资源数据
// 3. 统计访问数据
// 4. 生成统计报告
Thread.sleep(1000); // 模拟执行
logger.info("系统数据统计任务执行完成");
} catch (Exception e) {
logger.error("系统数据统计任务执行失败: ", e);
throw new RuntimeException("系统数据统计任务执行失败", e);
}
}
/**
* @description 执行带参数的统计任务
* @param params 参数
* @author yslg
* @since 2025-10-25
*/
public void execute(String params) {
logger.info("开始执行系统数据统计任务,参数: {}", params);
execute();
}
}

View File

@@ -0,0 +1,194 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.xyzh.crontab.mapper.CrontabLogMapper">
<!-- 结果映射 -->
<resultMap id="BaseResultMap" type="org.xyzh.common.dto.crontab.TbCrontabLog">
<id column="id" property="ID" />
<result column="task_id" property="taskId" />
<result column="task_name" property="taskName" />
<result column="task_group" property="taskGroup" />
<result column="bean_name" property="beanName" />
<result column="method_name" property="methodName" />
<result column="method_params" property="methodParams" />
<result column="execute_status" property="executeStatus" />
<result column="execute_message" property="executeMessage" />
<result column="exception_info" property="exceptionInfo" />
<result column="start_time" property="startTime" />
<result column="end_time" property="endTime" />
<result column="execute_duration" property="executeDuration" />
<result column="create_time" property="createTime" />
<result column="update_time" property="updateTime" />
<result column="delete_time" property="deleteTime" />
<result column="deleted" property="deleted" />
</resultMap>
<!-- 字段列表 -->
<sql id="Base_Column_List">
id, task_id, task_name, task_group, bean_name, method_name, method_params,
execute_status, execute_message, exception_info, start_time, end_time,
execute_duration, create_time, update_time, delete_time, deleted
</sql>
<!-- 查询条件 -->
<sql id="Base_Where_Clause">
<where>
<if test="filter != null">
<if test="filter.ID != null and filter.ID != ''">
AND id = #{filter.ID}
</if>
<if test="filter.taskId != null and filter.taskId != ''">
AND task_id = #{filter.taskId}
</if>
<if test="filter.taskName != null and filter.taskName != ''">
AND task_name LIKE CONCAT('%', #{filter.taskName}, '%')
</if>
<if test="filter.taskGroup != null and filter.taskGroup != ''">
AND task_group = #{filter.taskGroup}
</if>
<if test="filter.beanName != null and filter.beanName != ''">
AND bean_name = #{filter.beanName}
</if>
<if test="filter.executeStatus != null">
AND execute_status = #{filter.executeStatus}
</if>
<if test="filter.deleted != null">
AND deleted = #{filter.deleted}
</if>
</if>
</where>
</sql>
<!-- 插入日志 -->
<insert id="insertLog">
INSERT INTO tb_crontab_log
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="log.ID != null">id,</if>
<if test="log.taskId != null">task_id,</if>
<if test="log.taskName != null">task_name,</if>
<if test="log.taskGroup != null">task_group,</if>
<if test="log.beanName != null">bean_name,</if>
<if test="log.methodName != null">method_name,</if>
<if test="log.methodParams != null">method_params,</if>
<if test="log.executeStatus != null">execute_status,</if>
<if test="log.executeMessage != null">execute_message,</if>
<if test="log.exceptionInfo != null">exception_info,</if>
<if test="log.startTime != null">start_time,</if>
<if test="log.endTime != null">end_time,</if>
<if test="log.executeDuration != null">execute_duration,</if>
<if test="log.createTime != null">create_time,</if>
deleted
</trim>
VALUES
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="log.ID != null">#{log.ID},</if>
<if test="log.taskId != null">#{log.taskId},</if>
<if test="log.taskName != null">#{log.taskName},</if>
<if test="log.taskGroup != null">#{log.taskGroup},</if>
<if test="log.beanName != null">#{log.beanName},</if>
<if test="log.methodName != null">#{log.methodName},</if>
<if test="log.methodParams != null">#{log.methodParams},</if>
<if test="log.executeStatus != null">#{log.executeStatus},</if>
<if test="log.executeMessage != null">#{log.executeMessage},</if>
<if test="log.exceptionInfo != null">#{log.exceptionInfo},</if>
<if test="log.startTime != null">#{log.startTime},</if>
<if test="log.endTime != null">#{log.endTime},</if>
<if test="log.executeDuration != null">#{log.executeDuration},</if>
<if test="log.createTime != null">#{log.createTime},</if>
0
</trim>
</insert>
<!-- 根据ID查询日志 -->
<select id="selectLogById" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List" />
FROM tb_crontab_log
WHERE id = #{logId} AND deleted = 0
</select>
<!-- 根据任务ID查询日志列表 -->
<select id="selectLogsByTaskId" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List" />
FROM tb_crontab_log
WHERE task_id = #{taskId} AND deleted = 0
ORDER BY start_time DESC
</select>
<!-- 根据过滤条件查询日志列表 -->
<select id="selectLogList" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List" />
FROM tb_crontab_log
<include refid="Base_Where_Clause" />
ORDER BY start_time DESC
</select>
<!-- 分页查询日志列表 -->
<select id="selectLogPage" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List" />
FROM tb_crontab_log
<include refid="Base_Where_Clause" />
ORDER BY start_time DESC
LIMIT #{pageParam.pageSize} OFFSET #{pageParam.offset}
</select>
<!-- 统计日志总数 -->
<select id="countLogs" resultType="long">
SELECT COUNT(*)
FROM tb_crontab_log
<include refid="Base_Where_Clause" />
</select>
<!-- 删除日志(逻辑删除) -->
<update id="deleteLog">
UPDATE tb_crontab_log
SET deleted = 1,
delete_time = NOW()
WHERE id = #{logId} AND deleted = 0
</update>
<!-- 批量删除日志 -->
<update id="batchDeleteLogs">
UPDATE tb_crontab_log
SET deleted = 1,
delete_time = NOW()
WHERE deleted = 0
AND id IN
<foreach collection="ids" item="id" open="(" separator="," close=")">
#{id}
</foreach>
</update>
<!-- 清理指定时间之前的日志 -->
<update id="cleanLogsByDate">
UPDATE tb_crontab_log
SET deleted = 1,
delete_time = NOW()
WHERE deleted = 0
AND create_time &lt; #{beforeDate}
</update>
<!-- 根据任务ID和执行状态查询日志 -->
<select id="selectLogsByTaskIdAndStatus" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List" />
FROM tb_crontab_log
WHERE task_id = #{taskId}
AND execute_status = #{executeStatus}
AND deleted = 0
ORDER BY start_time DESC
</select>
<!-- 查询最近的执行日志 -->
<select id="selectRecentLogs" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List" />
FROM tb_crontab_log
WHERE task_id = #{taskId} AND deleted = 0
ORDER BY start_time DESC
LIMIT #{limit}
</select>
</mapper>

View File

@@ -0,0 +1,188 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="org.xyzh.crontab.mapper.CrontabTaskMapper">
<!-- 结果映射 -->
<resultMap id="BaseResultMap" type="org.xyzh.common.dto.crontab.TbCrontabTask">
<id column="id" property="ID" />
<result column="task_id" property="taskId" />
<result column="task_name" property="taskName" />
<result column="task_group" property="taskGroup" />
<result column="bean_name" property="beanName" />
<result column="method_name" property="methodName" />
<result column="method_params" property="methodParams" />
<result column="cron_expression" property="cronExpression" />
<result column="status" property="status" />
<result column="description" property="description" />
<result column="concurrent" property="concurrent" />
<result column="misfire_policy" property="misfirePolicy" />
<result column="creator" property="creator" />
<result column="updater" property="updater" />
<result column="create_time" property="createTime" />
<result column="update_time" property="updateTime" />
<result column="delete_time" property="deleteTime" />
<result column="deleted" property="deleted" />
</resultMap>
<!-- 字段列表 -->
<sql id="Base_Column_List">
id, task_id, task_name, task_group, bean_name, method_name, method_params,
cron_expression, status, description, concurrent, misfire_policy,
creator, updater, create_time, update_time, delete_time, deleted
</sql>
<!-- 查询条件 -->
<sql id="Base_Where_Clause">
<where>
<if test="filter != null">
<if test="filter.ID != null and filter.ID != ''">
AND id = #{filter.ID}
</if>
<if test="filter.taskId != null and filter.taskId != ''">
AND task_id = #{filter.taskId}
</if>
<if test="filter.taskName != null and filter.taskName != ''">
AND task_name LIKE CONCAT('%', #{filter.taskName}, '%')
</if>
<if test="filter.taskGroup != null and filter.taskGroup != ''">
AND task_group = #{filter.taskGroup}
</if>
<if test="filter.beanName != null and filter.beanName != ''">
AND bean_name = #{filter.beanName}
</if>
<if test="filter.methodName != null and filter.methodName != ''">
AND method_name = #{filter.methodName}
</if>
<if test="filter.status != null">
AND status = #{filter.status}
</if>
<if test="filter.deleted != null">
AND deleted = #{filter.deleted}
</if>
</if>
</where>
</sql>
<!-- 插入任务 -->
<insert id="insertTask">
INSERT INTO tb_crontab_task
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="task.ID != null">id,</if>
<if test="task.taskId != null">task_id,</if>
<if test="task.taskName != null">task_name,</if>
<if test="task.taskGroup != null">task_group,</if>
<if test="task.beanName != null">bean_name,</if>
<if test="task.methodName != null">method_name,</if>
<if test="task.methodParams != null">method_params,</if>
<if test="task.cronExpression != null">cron_expression,</if>
<if test="task.status != null">status,</if>
<if test="task.description != null">description,</if>
<if test="task.concurrent != null">concurrent,</if>
<if test="task.misfirePolicy != null">misfire_policy,</if>
<if test="task.creator != null">creator,</if>
<if test="task.createTime != null">create_time,</if>
deleted
</trim>
VALUES
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test="task.ID != null">#{task.ID},</if>
<if test="task.taskId != null">#{task.taskId},</if>
<if test="task.taskName != null">#{task.taskName},</if>
<if test="task.taskGroup != null">#{task.taskGroup},</if>
<if test="task.beanName != null">#{task.beanName},</if>
<if test="task.methodName != null">#{task.methodName},</if>
<if test="task.methodParams != null">#{task.methodParams},</if>
<if test="task.cronExpression != null">#{task.cronExpression},</if>
<if test="task.status != null">#{task.status},</if>
<if test="task.description != null">#{task.description},</if>
<if test="task.concurrent != null">#{task.concurrent},</if>
<if test="task.misfirePolicy != null">#{task.misfirePolicy},</if>
<if test="task.creator != null">#{task.creator},</if>
<if test="task.createTime != null">#{task.createTime},</if>
0
</trim>
</insert>
<!-- 更新任务 -->
<update id="updateTask">
UPDATE tb_crontab_task
<set>
<if test="task.taskName != null">task_name = #{task.taskName},</if>
<if test="task.taskGroup != null">task_group = #{task.taskGroup},</if>
<if test="task.beanName != null">bean_name = #{task.beanName},</if>
<if test="task.methodName != null">method_name = #{task.methodName},</if>
<if test="task.methodParams != null">method_params = #{task.methodParams},</if>
<if test="task.cronExpression != null">cron_expression = #{task.cronExpression},</if>
<if test="task.status != null">status = #{task.status},</if>
<if test="task.description != null">description = #{task.description},</if>
<if test="task.concurrent != null">concurrent = #{task.concurrent},</if>
<if test="task.misfirePolicy != null">misfire_policy = #{task.misfirePolicy},</if>
<if test="task.updater != null">updater = #{task.updater},</if>
update_time = NOW()
</set>
WHERE id = #{task.ID} AND deleted = 0
</update>
<!-- 删除任务(逻辑删除) -->
<update id="deleteTask">
UPDATE tb_crontab_task
SET deleted = 1,
delete_time = NOW()
WHERE id = #{taskId} AND deleted = 0
</update>
<!-- 根据ID查询任务 -->
<select id="selectTaskById" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List" />
FROM tb_crontab_task
WHERE id = #{taskId} AND deleted = 0
</select>
<!-- 根据过滤条件查询任务列表 -->
<select id="selectTaskList" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List" />
FROM tb_crontab_task
<include refid="Base_Where_Clause" />
ORDER BY create_time DESC
</select>
<!-- 分页查询任务列表 -->
<select id="selectTaskPage" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List" />
FROM tb_crontab_task
<include refid="Base_Where_Clause" />
ORDER BY create_time DESC
LIMIT #{pageParam.pageSize} OFFSET #{pageParam.offset}
</select>
<!-- 查询所有运行中的任务 -->
<select id="selectRunningTasks" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List" />
FROM tb_crontab_task
WHERE status = 1 AND deleted = 0
ORDER BY create_time DESC
</select>
<!-- 更新任务状态 -->
<update id="updateTaskStatus">
UPDATE tb_crontab_task
SET status = #{status},
update_time = NOW()
WHERE id = #{taskId} AND deleted = 0
</update>
<!-- 根据Bean名称和方法名称查询任务 -->
<select id="selectTaskByBeanAndMethod" resultMap="BaseResultMap">
SELECT
<include refid="Base_Column_List" />
FROM tb_crontab_task
WHERE bean_name = #{beanName}
AND method_name = #{methodName}
AND deleted = 0
LIMIT 1
</select>
</mapper>

View File

@@ -20,6 +20,7 @@
<module>achievement</module>
<module>ai</module>
<module>file</module>
<module>crontab</module>
</modules>
<properties>
@@ -117,6 +118,11 @@
<groupId>org.xyzh</groupId>
<artifactId>ai</artifactId>
<version>${school-news.version}</version>
</dependency>
<dependency>
<groupId>org.xyzh</groupId>
<artifactId>crontab</artifactId>
<version>${school-news.version}</version>
</dependency>
<!-- Spring相关依赖 -->
<!-- 覆盖SpringFramework的依赖配置-->