diff --git a/schoolNewsServ/.bin/mysql/sql/createTableCrontab.sql b/schoolNewsServ/.bin/mysql/sql/createTableCrontab.sql
new file mode 100644
index 0000000..7bdd6e0
--- /dev/null
+++ b/schoolNewsServ/.bin/mysql/sql/createTableCrontab.sql
@@ -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='定时任务执行日志表';
diff --git a/schoolNewsServ/.bin/mysql/sql/initAll.sql b/schoolNewsServ/.bin/mysql/sql/initAll.sql
index 50207fc..7735b92 100644
--- a/schoolNewsServ/.bin/mysql/sql/initAll.sql
+++ b/schoolNewsServ/.bin/mysql/sql/initAll.sql
@@ -37,6 +37,8 @@ SOURCE createTableSystem.sql;
SOURCE createTableAchievement.sql;
+SOURCE createTableCrontab.sql;
+
-- =====================================================
-- 插入初始数据
-- =====================================================
diff --git a/schoolNewsServ/.bin/mysql/sql/initMenuData.sql b/schoolNewsServ/.bin/mysql/sql/initMenuData.sql
index 92b69b1..7f2a11e 100644
--- a/schoolNewsServ/.bin/mysql/sql/initMenuData.sql
+++ b/schoolNewsServ/.bin/mysql/sql/initMenuData.sql
@@ -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());
diff --git a/schoolNewsServ/admin/pom.xml b/schoolNewsServ/admin/pom.xml
index 6ce664d..06e9f77 100644
--- a/schoolNewsServ/admin/pom.xml
+++ b/schoolNewsServ/admin/pom.xml
@@ -69,6 +69,11 @@
ai
${school-news.version}
+
+ org.xyzh
+ crontab
+ ${school-news.version}
+
diff --git a/schoolNewsServ/admin/src/main/java/org/xyzh/App.java b/schoolNewsServ/admin/src/main/java/org/xyzh/App.java
index 9d5b3f0..c12cee7 100644
--- a/schoolNewsServ/admin/src/main/java/org/xyzh/App.java
+++ b/schoolNewsServ/admin/src/main/java/org/xyzh/App.java
@@ -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) {
diff --git a/schoolNewsServ/api/api-all/pom.xml b/schoolNewsServ/api/api-all/pom.xml
index 7bb5571..2350034 100644
--- a/schoolNewsServ/api/api-all/pom.xml
+++ b/schoolNewsServ/api/api-all/pom.xml
@@ -53,5 +53,9 @@
org.xyzh
api-file
+
+ org.xyzh
+ api-crontab
+
\ No newline at end of file
diff --git a/schoolNewsServ/api/api-crontab/pom.xml b/schoolNewsServ/api/api-crontab/pom.xml
new file mode 100644
index 0000000..9eb26f6
--- /dev/null
+++ b/schoolNewsServ/api/api-crontab/pom.xml
@@ -0,0 +1,33 @@
+
+
+ 4.0.0
+
+ org.xyzh
+ api
+ ${school-news.version}
+
+
+ org.xyzh
+ api-crontab
+ ${school-news.version}
+
+
+ 21
+ 21
+
+
+
+
+ org.xyzh
+ common-core
+ ${school-news.version}
+
+
+ org.xyzh
+ common-dto
+ ${school-news.version}
+
+
+
diff --git a/schoolNewsServ/api/api-crontab/src/main/java/org/xyzh/api/crontab/CrontabService.java b/schoolNewsServ/api/api-crontab/src/main/java/org/xyzh/api/crontab/CrontabService.java
new file mode 100644
index 0000000..3aa2751
--- /dev/null
+++ b/schoolNewsServ/api/api-crontab/src/main/java/org/xyzh/api/crontab/CrontabService.java
@@ -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 创建结果
+ * @author yslg
+ * @since 2025-10-25
+ */
+ ResultDomain createTask(TbCrontabTask task);
+
+ /**
+ * @description 更新定时任务
+ * @param task 任务对象
+ * @return ResultDomain 更新结果
+ * @author yslg
+ * @since 2025-10-25
+ */
+ ResultDomain updateTask(TbCrontabTask task);
+
+ /**
+ * @description 删除定时任务
+ * @param taskId 任务ID
+ * @return ResultDomain 删除结果
+ * @author yslg
+ * @since 2025-10-25
+ */
+ ResultDomain deleteTask(String taskId);
+
+ /**
+ * @description 根据ID查询任务
+ * @param taskId 任务ID
+ * @return ResultDomain 任务信息
+ * @author yslg
+ * @since 2025-10-25
+ */
+ ResultDomain getTaskById(String taskId);
+
+ /**
+ * @description 根据过滤条件查询任务列表
+ * @param filter 过滤条件
+ * @return ResultDomain 任务列表
+ * @author yslg
+ * @since 2025-10-25
+ */
+ ResultDomain getTaskList(TbCrontabTask filter);
+
+ /**
+ * @description 分页查询任务列表
+ * @param filter 过滤条件
+ * @param pageParam 分页参数
+ * @return ResultDomain 任务列表
+ * @author yslg
+ * @since 2025-10-25
+ */
+ ResultDomain getTaskPage(TbCrontabTask filter, PageParam pageParam);
+
+ /**
+ * @description 启动定时任务
+ * @param taskId 任务ID
+ * @return ResultDomain 启动结果
+ * @author yslg
+ * @since 2025-10-25
+ */
+ ResultDomain startTask(String taskId);
+
+ /**
+ * @description 暂停定时任务
+ * @param taskId 任务ID
+ * @return ResultDomain 暂停结果
+ * @author yslg
+ * @since 2025-10-25
+ */
+ ResultDomain pauseTask(String taskId);
+
+ /**
+ * @description 立即执行一次任务
+ * @param taskId 任务ID
+ * @return ResultDomain 执行结果
+ * @author yslg
+ * @since 2025-10-25
+ */
+ ResultDomain executeTaskOnce(String taskId);
+
+ /**
+ * @description 验证Cron表达式
+ * @param cronExpression Cron表达式
+ * @return ResultDomain 验证结果(返回下次执行时间)
+ * @author yslg
+ * @since 2025-10-25
+ */
+ ResultDomain validateCronExpression(String cronExpression);
+
+ // ----------------定时任务日志--------------------------------
+
+ /**
+ * @description 根据任务ID查询执行日志
+ * @param taskId 任务ID
+ * @return ResultDomain 日志列表
+ * @author yslg
+ * @since 2025-10-25
+ */
+ ResultDomain getLogsByTaskId(String taskId);
+
+ /**
+ * @description 根据过滤条件查询日志列表
+ * @param filter 过滤条件
+ * @return ResultDomain 日志列表
+ * @author yslg
+ * @since 2025-10-25
+ */
+ ResultDomain getLogList(TbCrontabLog filter);
+
+ /**
+ * @description 分页查询日志列表
+ * @param filter 过滤条件
+ * @param pageParam 分页参数
+ * @return ResultDomain 日志列表
+ * @author yslg
+ * @since 2025-10-25
+ */
+ ResultDomain getLogPage(TbCrontabLog filter, PageParam pageParam);
+
+ /**
+ * @description 根据ID查询日志详情
+ * @param logId 日志ID
+ * @return ResultDomain 日志详情
+ * @author yslg
+ * @since 2025-10-25
+ */
+ ResultDomain getLogById(String logId);
+
+ /**
+ * @description 清理指定天数之前的日志
+ * @param days 天数
+ * @return ResultDomain 清理结果(返回清理条数)
+ * @author yslg
+ * @since 2025-10-25
+ */
+ ResultDomain cleanLogs(Integer days);
+
+ /**
+ * @description 删除任务执行日志
+ * @param logId 日志ID
+ * @return ResultDomain 删除结果
+ * @author yslg
+ * @since 2025-10-25
+ */
+ ResultDomain deleteLog(String logId);
+}
+
diff --git a/schoolNewsServ/api/pom.xml b/schoolNewsServ/api/pom.xml
index 2798bc2..472f3ea 100644
--- a/schoolNewsServ/api/pom.xml
+++ b/schoolNewsServ/api/pom.xml
@@ -23,6 +23,7 @@
api-study
api-news
api-file
+ api-crontab
@@ -82,6 +83,11 @@
api-file
${school-news.version}
+
+ org.xyzh
+ api-crontab
+ ${school-news.version}
+
diff --git a/schoolNewsServ/common/common-dto/src/main/java/org/xyzh/common/dto/crontab/TbCrontabLog.java b/schoolNewsServ/common/common-dto/src/main/java/org/xyzh/common/dto/crontab/TbCrontabLog.java
new file mode 100644
index 0000000..4ac36b9
--- /dev/null
+++ b/schoolNewsServ/common/common-dto/src/main/java/org/xyzh/common/dto/crontab/TbCrontabLog.java
@@ -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 +
+ '}';
+ }
+}
+
diff --git a/schoolNewsServ/common/common-dto/src/main/java/org/xyzh/common/dto/crontab/TbCrontabTask.java b/schoolNewsServ/common/common-dto/src/main/java/org/xyzh/common/dto/crontab/TbCrontabTask.java
new file mode 100644
index 0000000..97be520
--- /dev/null
+++ b/schoolNewsServ/common/common-dto/src/main/java/org/xyzh/common/dto/crontab/TbCrontabTask.java
@@ -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 +
+ '}';
+ }
+}
+
diff --git a/schoolNewsServ/common/common-dto/src/main/java/org/xyzh/common/vo/CrontabLogVO.java b/schoolNewsServ/common/common-dto/src/main/java/org/xyzh/common/vo/CrontabLogVO.java
new file mode 100644
index 0000000..ce90e76
--- /dev/null
+++ b/schoolNewsServ/common/common-dto/src/main/java/org/xyzh/common/vo/CrontabLogVO.java
@@ -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 +
+ '}';
+ }
+}
+
diff --git a/schoolNewsServ/common/common-dto/src/main/java/org/xyzh/common/vo/CrontabTaskVO.java b/schoolNewsServ/common/common-dto/src/main/java/org/xyzh/common/vo/CrontabTaskVO.java
new file mode 100644
index 0000000..88e9097
--- /dev/null
+++ b/schoolNewsServ/common/common-dto/src/main/java/org/xyzh/common/vo/CrontabTaskVO.java
@@ -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 +
+ '}';
+ }
+}
+
diff --git a/schoolNewsServ/crontab/README.md b/schoolNewsServ/crontab/README.md
new file mode 100644
index 0000000..53ec63c
--- /dev/null
+++ b/schoolNewsServ/crontab/README.md
@@ -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
+```
+
+#### 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
+```
+
+#### 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
+
diff --git a/schoolNewsServ/crontab/pom.xml b/schoolNewsServ/crontab/pom.xml
new file mode 100644
index 0000000..c38662e
--- /dev/null
+++ b/schoolNewsServ/crontab/pom.xml
@@ -0,0 +1,90 @@
+
+
+ 4.0.0
+
+ org.xyzh
+ school-news
+ ${school-news.version}
+
+
+ org.xyzh
+ crontab
+ ${school-news.version}
+
+
+ 21
+ 21
+
+
+
+
+
+ org.xyzh
+ api-crontab
+ ${school-news.version}
+
+
+
+
+ org.xyzh
+ common-all
+ ${school-news.version}
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+ org.springframework.boot
+ spring-boot-starter-logging
+
+
+
+
+
+
+ com.baomidou
+ mybatis-plus-spring-boot3-starter
+
+
+
+
+ com.mysql
+ mysql-connector-j
+
+
+
+
+ com.zaxxer
+ HikariCP
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-log4j2
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-quartz
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-compiler-plugin
+
+ true
+
+
+
+
+
diff --git a/schoolNewsServ/crontab/src/main/java/org/xyzh/crontab/config/SchedulerConfig.java b/schoolNewsServ/crontab/src/main/java/org/xyzh/crontab/config/SchedulerConfig.java
new file mode 100644
index 0000000..4e4aaeb
--- /dev/null
+++ b/schoolNewsServ/crontab/src/main/java/org/xyzh/crontab/config/SchedulerConfig.java
@@ -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;
+ }
+}
+
diff --git a/schoolNewsServ/crontab/src/main/java/org/xyzh/crontab/controller/CrontabController.java b/schoolNewsServ/crontab/src/main/java/org/xyzh/crontab/controller/CrontabController.java
new file mode 100644
index 0000000..ef30cc6
--- /dev/null
+++ b/schoolNewsServ/crontab/src/main/java/org/xyzh/crontab/controller/CrontabController.java
@@ -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
+ * @author yslg
+ * @since 2025-10-25
+ */
+ @PostMapping("/task")
+ public ResultDomain createTask(@RequestBody TbCrontabTask task) {
+ return crontabService.createTask(task);
+ }
+
+ /**
+ * @description 更新定时任务
+ * @param task 任务对象
+ * @return ResultDomain
+ * @author yslg
+ * @since 2025-10-25
+ */
+ @PutMapping("/task")
+ public ResultDomain updateTask(@RequestBody TbCrontabTask task) {
+ return crontabService.updateTask(task);
+ }
+
+ /**
+ * @description 删除定时任务
+ * @param task 任务对象
+ * @return ResultDomain
+ * @author yslg
+ * @since 2025-10-25
+ */
+ @DeleteMapping("/task")
+ public ResultDomain deleteTask(@RequestBody TbCrontabTask task) {
+ return crontabService.deleteTask(task.getID());
+ }
+
+ /**
+ * @description 根据ID查询任务
+ * @param taskId 任务ID
+ * @return ResultDomain
+ * @author yslg
+ * @since 2025-10-25
+ */
+ @GetMapping("/task/{taskId}")
+ public ResultDomain getTaskById(@PathVariable String taskId) {
+ return crontabService.getTaskById(taskId);
+ }
+
+ /**
+ * @description 查询任务列表
+ * @param filter 过滤条件
+ * @return ResultDomain
+ * @author yslg
+ * @since 2025-10-25
+ */
+ @PostMapping("/task/list")
+ public ResultDomain getTaskList(@RequestBody TbCrontabTask filter) {
+ return crontabService.getTaskList(filter);
+ }
+
+ /**
+ * @description 分页查询任务列表
+ * @param pageRequest 分页请求对象
+ * @return ResultDomain
+ * @author yslg
+ * @since 2025-10-25
+ */
+ @PostMapping("/task/page")
+ public ResultDomain getTaskPage(@RequestBody PageRequest pageRequest) {
+ TbCrontabTask filter = pageRequest.getFilter();
+ PageParam pageParam = pageRequest.getPageParam();
+ return crontabService.getTaskPage(filter, pageParam);
+ }
+
+ /**
+ * @description 启动定时任务
+ * @param taskId 任务ID
+ * @return ResultDomain
+ * @author yslg
+ * @since 2025-10-25
+ */
+ @PostMapping("/task/start/{taskId}")
+ public ResultDomain startTask(@PathVariable String taskId) {
+ return crontabService.startTask(taskId);
+ }
+
+ /**
+ * @description 暂停定时任务
+ * @param taskId 任务ID
+ * @return ResultDomain
+ * @author yslg
+ * @since 2025-10-25
+ */
+ @PostMapping("/task/pause/{taskId}")
+ public ResultDomain pauseTask(@PathVariable String taskId) {
+ return crontabService.pauseTask(taskId);
+ }
+
+ /**
+ * @description 立即执行一次任务
+ * @param taskId 任务ID
+ * @return ResultDomain
+ * @author yslg
+ * @since 2025-10-25
+ */
+ @PostMapping("/task/execute/{taskId}")
+ public ResultDomain executeTaskOnce(@PathVariable String taskId) {
+ return crontabService.executeTaskOnce(taskId);
+ }
+
+ /**
+ * @description 验证Cron表达式
+ * @param cronExpression Cron表达式
+ * @return ResultDomain
+ * @author yslg
+ * @since 2025-10-25
+ */
+ @GetMapping("/task/validate")
+ public ResultDomain validateCronExpression(@RequestParam String cronExpression) {
+ return crontabService.validateCronExpression(cronExpression);
+ }
+
+ // ----------------定时任务日志--------------------------------
+
+ /**
+ * @description 根据任务ID查询日志
+ * @param taskId 任务ID
+ * @return ResultDomain
+ * @author yslg
+ * @since 2025-10-25
+ */
+ @GetMapping("/log/task/{taskId}")
+ public ResultDomain getLogsByTaskId(@PathVariable String taskId) {
+ return crontabService.getLogsByTaskId(taskId);
+ }
+
+ /**
+ * @description 查询日志列表
+ * @param filter 过滤条件
+ * @return ResultDomain
+ * @author yslg
+ * @since 2025-10-25
+ */
+ @PostMapping("/log/list")
+ public ResultDomain getLogList(@RequestBody TbCrontabLog filter) {
+ return crontabService.getLogList(filter);
+ }
+
+ /**
+ * @description 分页查询日志列表
+ * @param pageRequest 分页请求对象
+ * @return ResultDomain
+ * @author yslg
+ * @since 2025-10-25
+ */
+ @PostMapping("/log/page")
+ public ResultDomain getLogPage(@RequestBody PageRequest pageRequest) {
+ TbCrontabLog filter = pageRequest.getFilter();
+ PageParam pageParam = pageRequest.getPageParam();
+ return crontabService.getLogPage(filter, pageParam);
+ }
+
+ /**
+ * @description 根据ID查询日志详情
+ * @param logId 日志ID
+ * @return ResultDomain
+ * @author yslg
+ * @since 2025-10-25
+ */
+ @GetMapping("/log/{logId}")
+ public ResultDomain getLogById(@PathVariable String logId) {
+ return crontabService.getLogById(logId);
+ }
+
+ /**
+ * @description 清理指定天数之前的日志
+ * @param days 天数
+ * @return ResultDomain
+ * @author yslg
+ * @since 2025-10-25
+ */
+ @DeleteMapping("/log/clean/{days}")
+ public ResultDomain cleanLogs(@PathVariable Integer days) {
+ return crontabService.cleanLogs(days);
+ }
+
+ /**
+ * @description 删除日志
+ * @param log 日志对象
+ * @return ResultDomain
+ * @author yslg
+ * @since 2025-10-25
+ */
+ @DeleteMapping("/log")
+ public ResultDomain deleteLog(@RequestBody TbCrontabLog log) {
+ return crontabService.deleteLog(log.getID());
+ }
+}
+
diff --git a/schoolNewsServ/crontab/src/main/java/org/xyzh/crontab/mapper/CrontabLogMapper.java b/schoolNewsServ/crontab/src/main/java/org/xyzh/crontab/mapper/CrontabLogMapper.java
new file mode 100644
index 0000000..9620508
--- /dev/null
+++ b/schoolNewsServ/crontab/src/main/java/org/xyzh/crontab/mapper/CrontabLogMapper.java
@@ -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 {
+
+ /**
+ * @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 日志列表
+ * @author yslg
+ * @since 2025-10-25
+ */
+ List selectLogsByTaskId(@Param("taskId") String taskId);
+
+ /**
+ * @description 根据过滤条件查询日志列表
+ * @param filter 过滤条件
+ * @return List 日志列表
+ * @author yslg
+ * @since 2025-10-25
+ */
+ List selectLogList(@Param("filter") TbCrontabLog filter);
+
+ /**
+ * @description 分页查询日志列表
+ * @param filter 过滤条件
+ * @param pageParam 分页参数
+ * @return List 日志列表
+ * @author yslg
+ * @since 2025-10-25
+ */
+ List 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 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 日志列表
+ * @author yslg
+ * @since 2025-10-25
+ */
+ List selectLogsByTaskIdAndStatus(@Param("taskId") String taskId, @Param("executeStatus") Integer executeStatus);
+
+ /**
+ * @description 查询最近的执行日志
+ * @param taskId 任务ID
+ * @param limit 限制数量
+ * @return List 日志列表
+ * @author yslg
+ * @since 2025-10-25
+ */
+ List selectRecentLogs(@Param("taskId") String taskId, @Param("limit") Integer limit);
+}
+
diff --git a/schoolNewsServ/crontab/src/main/java/org/xyzh/crontab/mapper/CrontabTaskMapper.java b/schoolNewsServ/crontab/src/main/java/org/xyzh/crontab/mapper/CrontabTaskMapper.java
new file mode 100644
index 0000000..8ca15c1
--- /dev/null
+++ b/schoolNewsServ/crontab/src/main/java/org/xyzh/crontab/mapper/CrontabTaskMapper.java
@@ -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 {
+
+ /**
+ * @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 任务列表
+ * @author yslg
+ * @since 2025-10-25
+ */
+ List selectTaskList(@Param("filter") TbCrontabTask filter);
+
+ /**
+ * @description 分页查询任务列表
+ * @param filter 过滤条件
+ * @param pageParam 分页参数
+ * @return List 任务列表
+ * @author yslg
+ * @since 2025-10-25
+ */
+ List selectTaskPage(@Param("filter") TbCrontabTask filter, @Param("pageParam") PageParam pageParam);
+
+ /**
+ * @description 查询所有运行中的任务
+ * @return List 任务列表
+ * @author yslg
+ * @since 2025-10-25
+ */
+ List 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);
+}
+
diff --git a/schoolNewsServ/crontab/src/main/java/org/xyzh/crontab/scheduler/SchedulerInitializer.java b/schoolNewsServ/crontab/src/main/java/org/xyzh/crontab/scheduler/SchedulerInitializer.java
new file mode 100644
index 0000000..b2ca69c
--- /dev/null
+++ b/schoolNewsServ/crontab/src/main/java/org/xyzh/crontab/scheduler/SchedulerInitializer.java
@@ -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 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);
+ }
+ }
+}
+
diff --git a/schoolNewsServ/crontab/src/main/java/org/xyzh/crontab/scheduler/SchedulerManager.java b/schoolNewsServ/crontab/src/main/java/org/xyzh/crontab/scheduler/SchedulerManager.java
new file mode 100644
index 0000000..38d4b7a
--- /dev/null
+++ b/schoolNewsServ/crontab/src/main/java/org/xyzh/crontab/scheduler/SchedulerManager.java
@@ -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> 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("已清除所有任务调度");
+ }
+}
+
diff --git a/schoolNewsServ/crontab/src/main/java/org/xyzh/crontab/scheduler/TaskExecutor.java b/schoolNewsServ/crontab/src/main/java/org/xyzh/crontab/scheduler/TaskExecutor.java
new file mode 100644
index 0000000..e4dc9ba
--- /dev/null
+++ b/schoolNewsServ/crontab/src/main/java/org/xyzh/crontab/scheduler/TaskExecutor.java
@@ -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();
+ }
+}
+
diff --git a/schoolNewsServ/crontab/src/main/java/org/xyzh/crontab/service/impl/CrontabServiceImpl.java b/schoolNewsServ/crontab/src/main/java/org/xyzh/crontab/service/impl/CrontabServiceImpl.java
new file mode 100644
index 0000000..0ff3e44
--- /dev/null
+++ b/schoolNewsServ/crontab/src/main/java/org/xyzh/crontab/service/impl/CrontabServiceImpl.java
@@ -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 createTask(TbCrontabTask task) {
+ ResultDomain 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 updateTask(TbCrontabTask task) {
+ ResultDomain 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 deleteTask(String taskId) {
+ ResultDomain 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 getTaskById(String taskId) {
+ ResultDomain 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 getTaskList(TbCrontabTask filter) {
+ ResultDomain resultDomain = new ResultDomain<>();
+ try {
+ if (filter == null) {
+ filter = new TbCrontabTask();
+ }
+ filter.setDeleted(false);
+
+ List list = taskMapper.selectTaskList(filter);
+ resultDomain.success("查询成功", list);
+ } catch (Exception e) {
+ logger.error("查询定时任务列表异常: ", e);
+ resultDomain.fail("查询定时任务列表异常: " + e.getMessage());
+ }
+ return resultDomain;
+ }
+
+ @Override
+ public ResultDomain getTaskPage(TbCrontabTask filter, PageParam pageParam) {
+ ResultDomain resultDomain = new ResultDomain<>();
+ try {
+ if (filter == null) {
+ filter = new TbCrontabTask();
+ }
+ filter.setDeleted(false);
+
+ if (pageParam == null) {
+ pageParam = new PageParam();
+ }
+
+ List 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 startTask(String taskId) {
+ ResultDomain 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 pauseTask(String taskId) {
+ ResultDomain 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 executeTaskOnce(String taskId) {
+ ResultDomain 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 validateCronExpression(String cronExpression) {
+ ResultDomain 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 getLogsByTaskId(String taskId) {
+ ResultDomain resultDomain = new ResultDomain<>();
+ try {
+ if (taskId == null || taskId.isEmpty()) {
+ resultDomain.fail("任务ID不能为空");
+ return resultDomain;
+ }
+
+ List list = logMapper.selectLogsByTaskId(taskId);
+ resultDomain.success("查询成功", list);
+ } catch (Exception e) {
+ logger.error("查询任务日志异常: ", e);
+ resultDomain.fail("查询任务日志异常: " + e.getMessage());
+ }
+ return resultDomain;
+ }
+
+ @Override
+ public ResultDomain getLogList(TbCrontabLog filter) {
+ ResultDomain resultDomain = new ResultDomain<>();
+ try {
+ if (filter == null) {
+ filter = new TbCrontabLog();
+ }
+ filter.setDeleted(false);
+
+ List list = logMapper.selectLogList(filter);
+ resultDomain.success("查询成功", list);
+ } catch (Exception e) {
+ logger.error("查询日志列表异常: ", e);
+ resultDomain.fail("查询日志列表异常: " + e.getMessage());
+ }
+ return resultDomain;
+ }
+
+ @Override
+ public ResultDomain getLogPage(TbCrontabLog filter, PageParam pageParam) {
+ ResultDomain resultDomain = new ResultDomain<>();
+ try {
+ if (filter == null) {
+ filter = new TbCrontabLog();
+ }
+ filter.setDeleted(false);
+
+ if (pageParam == null) {
+ pageParam = new PageParam();
+ }
+
+ List list = logMapper.selectLogPage(filter, pageParam);
+ resultDomain.success("查询成功", list);
+ } catch (Exception e) {
+ logger.error("分页查询日志异常: ", e);
+ resultDomain.fail("分页查询日志异常: " + e.getMessage());
+ }
+ return resultDomain;
+ }
+
+ @Override
+ public ResultDomain getLogById(String logId) {
+ ResultDomain 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 cleanLogs(Integer days) {
+ ResultDomain 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 deleteLog(String logId) {
+ ResultDomain 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;
+ }
+}
diff --git a/schoolNewsServ/crontab/src/main/java/org/xyzh/crontab/task/DataBackupTask.java b/schoolNewsServ/crontab/src/main/java/org/xyzh/crontab/task/DataBackupTask.java
new file mode 100644
index 0000000..18126ed
--- /dev/null
+++ b/schoolNewsServ/crontab/src/main/java/org/xyzh/crontab/task/DataBackupTask.java
@@ -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();
+ }
+}
+
diff --git a/schoolNewsServ/crontab/src/main/java/org/xyzh/crontab/task/LogCleanTask.java b/schoolNewsServ/crontab/src/main/java/org/xyzh/crontab/task/LogCleanTask.java
new file mode 100644
index 0000000..d20f007
--- /dev/null
+++ b/schoolNewsServ/crontab/src/main/java/org/xyzh/crontab/task/LogCleanTask.java
@@ -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);
+ }
+ }
+}
+
diff --git a/schoolNewsServ/crontab/src/main/java/org/xyzh/crontab/task/SystemStatisticsTask.java b/schoolNewsServ/crontab/src/main/java/org/xyzh/crontab/task/SystemStatisticsTask.java
new file mode 100644
index 0000000..65547fe
--- /dev/null
+++ b/schoolNewsServ/crontab/src/main/java/org/xyzh/crontab/task/SystemStatisticsTask.java
@@ -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();
+ }
+}
+
diff --git a/schoolNewsServ/crontab/src/main/resources/mapper/CrontabLogMapper.xml b/schoolNewsServ/crontab/src/main/resources/mapper/CrontabLogMapper.xml
new file mode 100644
index 0000000..64a13c1
--- /dev/null
+++ b/schoolNewsServ/crontab/src/main/resources/mapper/CrontabLogMapper.xml
@@ -0,0 +1,194 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 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
+
+
+
+
+
+
+
+ AND id = #{filter.ID}
+
+
+ AND task_id = #{filter.taskId}
+
+
+ AND task_name LIKE CONCAT('%', #{filter.taskName}, '%')
+
+
+ AND task_group = #{filter.taskGroup}
+
+
+ AND bean_name = #{filter.beanName}
+
+
+ AND execute_status = #{filter.executeStatus}
+
+
+ AND deleted = #{filter.deleted}
+
+
+
+
+
+
+
+ INSERT INTO tb_crontab_log
+
+ 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,
+ deleted
+
+ VALUES
+
+ #{log.ID},
+ #{log.taskId},
+ #{log.taskName},
+ #{log.taskGroup},
+ #{log.beanName},
+ #{log.methodName},
+ #{log.methodParams},
+ #{log.executeStatus},
+ #{log.executeMessage},
+ #{log.exceptionInfo},
+ #{log.startTime},
+ #{log.endTime},
+ #{log.executeDuration},
+ #{log.createTime},
+ 0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ UPDATE tb_crontab_log
+ SET deleted = 1,
+ delete_time = NOW()
+ WHERE id = #{logId} AND deleted = 0
+
+
+
+
+ UPDATE tb_crontab_log
+ SET deleted = 1,
+ delete_time = NOW()
+ WHERE deleted = 0
+ AND id IN
+
+ #{id}
+
+
+
+
+
+ UPDATE tb_crontab_log
+ SET deleted = 1,
+ delete_time = NOW()
+ WHERE deleted = 0
+ AND create_time < #{beforeDate}
+
+
+
+
+
+
+
+
diff --git a/schoolNewsServ/crontab/src/main/resources/mapper/CrontabTaskMapper.xml b/schoolNewsServ/crontab/src/main/resources/mapper/CrontabTaskMapper.xml
new file mode 100644
index 0000000..5cae457
--- /dev/null
+++ b/schoolNewsServ/crontab/src/main/resources/mapper/CrontabTaskMapper.xml
@@ -0,0 +1,188 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 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
+
+
+
+
+
+
+
+ AND id = #{filter.ID}
+
+
+ AND task_id = #{filter.taskId}
+
+
+ AND task_name LIKE CONCAT('%', #{filter.taskName}, '%')
+
+
+ AND task_group = #{filter.taskGroup}
+
+
+ AND bean_name = #{filter.beanName}
+
+
+ AND method_name = #{filter.methodName}
+
+
+ AND status = #{filter.status}
+
+
+ AND deleted = #{filter.deleted}
+
+
+
+
+
+
+
+ INSERT INTO tb_crontab_task
+
+ id,
+ task_id,
+ task_name,
+ task_group,
+ bean_name,
+ method_name,
+ method_params,
+ cron_expression,
+ status,
+ description,
+ concurrent,
+ misfire_policy,
+ creator,
+ create_time,
+ deleted
+
+ VALUES
+
+ #{task.ID},
+ #{task.taskId},
+ #{task.taskName},
+ #{task.taskGroup},
+ #{task.beanName},
+ #{task.methodName},
+ #{task.methodParams},
+ #{task.cronExpression},
+ #{task.status},
+ #{task.description},
+ #{task.concurrent},
+ #{task.misfirePolicy},
+ #{task.creator},
+ #{task.createTime},
+ 0
+
+
+
+
+
+ UPDATE tb_crontab_task
+
+ task_name = #{task.taskName},
+ task_group = #{task.taskGroup},
+ bean_name = #{task.beanName},
+ method_name = #{task.methodName},
+ method_params = #{task.methodParams},
+ cron_expression = #{task.cronExpression},
+ status = #{task.status},
+ description = #{task.description},
+ concurrent = #{task.concurrent},
+ misfire_policy = #{task.misfirePolicy},
+ updater = #{task.updater},
+ update_time = NOW()
+
+ WHERE id = #{task.ID} AND deleted = 0
+
+
+
+
+ UPDATE tb_crontab_task
+ SET deleted = 1,
+ delete_time = NOW()
+ WHERE id = #{taskId} AND deleted = 0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ UPDATE tb_crontab_task
+ SET status = #{status},
+ update_time = NOW()
+ WHERE id = #{taskId} AND deleted = 0
+
+
+
+
+
diff --git a/schoolNewsServ/pom.xml b/schoolNewsServ/pom.xml
index fa7ecc0..e7c4460 100644
--- a/schoolNewsServ/pom.xml
+++ b/schoolNewsServ/pom.xml
@@ -20,6 +20,7 @@
achievement
ai
file
+ crontab
@@ -117,6 +118,11 @@
org.xyzh
ai
${school-news.version}
+
+
+ org.xyzh
+ crontab
+ ${school-news.version}