diff --git a/coder-common-thin-model/src/main/java/org/leocoder/thin/domain/pojo/system/SysJob.java b/coder-common-thin-model/src/main/java/org/leocoder/thin/domain/pojo/system/SysJob.java
new file mode 100644
index 0000000..5864dec
--- /dev/null
+++ b/coder-common-thin-model/src/main/java/org/leocoder/thin/domain/pojo/system/SysJob.java
@@ -0,0 +1,149 @@
+package org.leocoder.thin.domain.pojo.system;
+
+import com.baomidou.mybatisplus.annotation.*;
+import com.fasterxml.jackson.annotation.JsonFormat;
+import jakarta.validation.constraints.NotBlank;
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+
+import java.io.Serializable;
+import java.time.LocalDateTime;
+
+/**
+ * @author Leocoder
+ * @description [定时任务-模型]
+ */
+@Data
+@AllArgsConstructor
+@NoArgsConstructor
+@TableName("sys_job")
+public class SysJob implements Serializable {
+
+ /**
+ * 任务ID
+ */
+ @TableId(value = "job_id", type = IdType.ASSIGN_ID)
+ private Long jobId;
+
+ /**
+ * 任务名称
+ */
+ @NotBlank(message = "任务名称不能为空")
+ @TableField("job_name")
+ private String jobName;
+
+ /**
+ * 任务类型[1-管理平台 2-小程序 3-App]
+ */
+ @NotBlank(message = "任务类型不能为空")
+ @TableField("job_type")
+ private String jobType;
+
+ /**
+ * 类路径
+ */
+ @NotBlank(message = "类路径不能为空")
+ @TableField("class_path")
+ private String classPath;
+
+ /**
+ * 方法名称
+ */
+ @NotBlank(message = "方法名称不能为空")
+ @TableField("method_name")
+ private String methodName;
+
+ /**
+ * cron执行表达式
+ */
+ @NotBlank(message = "cron执行表达式不能为空")
+ @TableField("cron_expression")
+ private String cronExpression;
+
+ /**
+ * cron计划策略[1-立即执行 2-执行一次 3-放弃执行]
+ */
+ @NotBlank(message = "cron计划策略不能为空")
+ @TableField("policy_status")
+ private String policyStatus;
+
+ /**
+ * 任务状态 [0正常 1暂停]
+ */
+ @NotBlank(message = "任务状态不能为空")
+ @TableField("job_status")
+ private String jobStatus;
+
+ /**
+ * 任务参数
+ */
+ @TableField("job_params")
+ private String jobParams;
+
+ /**
+ * 任务备注
+ */
+ @TableField("remark")
+ private String remark;
+
+ /**
+ * 创建者
+ */
+ @TableField("create_by")
+ private String createBy;
+
+ /**
+ * 创建时间
+ */
+ @TableField(value = "create_time", fill = FieldFill.INSERT)
+ @JsonFormat(shape = JsonFormat.Shape.STRING, pattern="yyyy-MM-dd HH:mm:ss")
+ private LocalDateTime createTime;
+
+ /**
+ * 更新者
+ */
+ @TableField("update_by")
+ private String updateBy;
+
+ /**
+ * 更新时间
+ */
+ @TableField(value = "update_time", fill = FieldFill.INSERT_UPDATE)
+ @JsonFormat(shape = JsonFormat.Shape.STRING, pattern="yyyy-MM-dd HH:mm:ss")
+ private LocalDateTime updateTime;
+
+ /**
+ * @description [获取任务状态文本]
+ * @author Leocoder
+ */
+ public String getJobStatusText() {
+ return "0".equals(jobStatus) ? "正常" : "暂停";
+ }
+
+ /**
+ * @description [获取策略状态文本]
+ * @author Leocoder
+ */
+ public String getPolicyStatusText() {
+ switch (policyStatus) {
+ case "1": return "立即执行";
+ case "2": return "执行一次";
+ case "3": return "放弃执行";
+ default: return "未知";
+ }
+ }
+
+ /**
+ * @description [获取任务类型文本]
+ * @author Leocoder
+ */
+ public String getJobTypeText() {
+ switch (jobType) {
+ case "1": return "管理平台";
+ case "2": return "小程序";
+ case "3": return "App";
+ default: return "其他";
+ }
+ }
+}
\ No newline at end of file
diff --git a/coder-common-thin-plugins/coder-common-thin-job/pom.xml b/coder-common-thin-plugins/coder-common-thin-job/pom.xml
new file mode 100644
index 0000000..b6e6c5a
--- /dev/null
+++ b/coder-common-thin-plugins/coder-common-thin-job/pom.xml
@@ -0,0 +1,85 @@
+
+
+ 4.0.0
+
+ org.leocoder.thin
+ coder-common-thin-plugins
+ ${revision}
+
+
+ coder-common-thin-job
+ 定时任务插件模块
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-web
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-validation
+
+
+
+
+ com.baomidou
+ mybatis-plus-spring-boot3-starter
+
+
+
+
+ cn.dev33
+ sa-token-spring-boot3-starter
+
+
+
+
+ cn.hutool
+ hutool-all
+
+
+
+
+ org.apache.commons
+ commons-lang3
+
+
+
+
+ org.springdoc
+ springdoc-openapi-starter-webmvc-ui
+
+
+
+
+ org.projectlombok
+ lombok
+ true
+
+
+
+
+ org.leocoder.thin
+ coder-common-thin-common
+ ${revision}
+
+
+
+ org.leocoder.thin
+ coder-common-thin-model
+ ${revision}
+
+
+
+ org.leocoder.thin
+ coder-common-thin-mybatisplus
+ ${revision}
+
+
+
+
\ No newline at end of file
diff --git a/coder-common-thin-plugins/coder-common-thin-job/src/main/java/org/leocoder/thin/job/anno/EnableCoderJob.java b/coder-common-thin-plugins/coder-common-thin-job/src/main/java/org/leocoder/thin/job/anno/EnableCoderJob.java
new file mode 100644
index 0000000..9dbac7a
--- /dev/null
+++ b/coder-common-thin-plugins/coder-common-thin-job/src/main/java/org/leocoder/thin/job/anno/EnableCoderJob.java
@@ -0,0 +1,19 @@
+package org.leocoder.thin.job.anno;
+
+import org.leocoder.thin.job.config.JobConfiguration;
+import org.springframework.context.annotation.Import;
+
+import java.lang.annotation.*;
+
+/**
+ * @author Leocoder
+ * @description [启用定时任务插件注解]
+ */
+@Target({ElementType.TYPE})
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+@Inherited
+@Import({JobConfiguration.class})
+public @interface EnableCoderJob {
+
+}
\ No newline at end of file
diff --git a/coder-common-thin-plugins/coder-common-thin-job/src/main/java/org/leocoder/thin/job/config/JobConfiguration.java b/coder-common-thin-plugins/coder-common-thin-job/src/main/java/org/leocoder/thin/job/config/JobConfiguration.java
new file mode 100644
index 0000000..365f709
--- /dev/null
+++ b/coder-common-thin-plugins/coder-common-thin-job/src/main/java/org/leocoder/thin/job/config/JobConfiguration.java
@@ -0,0 +1,20 @@
+package org.leocoder.thin.job.config;
+
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+
+/**
+ * @author Leocoder
+ * @description [定时任务插件配置类]
+ */
+@Slf4j
+@Configuration
+@ComponentScan(basePackages = "org.leocoder.thin.job")
+public class JobConfiguration {
+
+ public JobConfiguration() {
+ log.info("定时任务插件已启用");
+ }
+
+}
\ No newline at end of file
diff --git a/coder-common-thin-plugins/coder-common-thin-job/src/main/java/org/leocoder/thin/job/controller/SysJobController.java b/coder-common-thin-plugins/coder-common-thin-job/src/main/java/org/leocoder/thin/job/controller/SysJobController.java
new file mode 100644
index 0000000..e3c2a44
--- /dev/null
+++ b/coder-common-thin-plugins/coder-common-thin-job/src/main/java/org/leocoder/thin/job/controller/SysJobController.java
@@ -0,0 +1,201 @@
+package org.leocoder.thin.job.controller;
+
+import cn.dev33.satoken.annotation.SaCheckPermission;
+import cn.hutool.cron.CronUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.metadata.IPage;
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.StringUtils;
+import org.leocoder.thin.domain.model.vo.system.SysJobVo;
+import org.leocoder.thin.domain.pojo.system.SysJob;
+import org.leocoder.thin.job.service.SysJobService;
+import org.springframework.transaction.annotation.Transactional;
+import org.springframework.web.bind.annotation.*;
+
+import java.util.List;
+
+/**
+ * @author Leocoder
+ * @description [定时任务管理控制器]
+ */
+@Tag(name = "定时任务管理", description = "系统定时任务的增删改查和执行控制")
+@Slf4j
+@RequestMapping("/coder")
+@RequiredArgsConstructor
+@RestController
+public class SysJobController {
+
+ private final SysJobService sysJobService;
+
+ /**
+ * @description [多条件分页查询]
+ * @author Leocoder
+ */
+ @Operation(summary = "分页查询定时任务", description = "根据查询条件分页获取系统定时任务列表")
+ @SaCheckPermission("monitor:job:list")
+ @GetMapping("/sysJob/listPage")
+ public IPage listPage(SysJobVo vo) {
+ // 分页构造器
+ Page page = new Page<>(vo.getPageNo(), vo.getPageSize());
+ // 条件构造器
+ LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>();
+ wrapper.like(StringUtils.isNotBlank(vo.getJobName()), SysJob::getJobName, vo.getJobName());
+ wrapper.eq(StringUtils.isNotBlank(vo.getJobType()), SysJob::getJobType, vo.getJobType());
+ wrapper.eq(StringUtils.isNotBlank(vo.getJobStatus()), SysJob::getJobStatus, vo.getJobStatus());
+ wrapper.orderByDesc(SysJob::getCreateTime);
+ // 进行分页查询
+ page = sysJobService.page(page, wrapper);
+ return page;
+ }
+
+ /**
+ * @description [查询所有]
+ * @author Leocoder
+ */
+ @Operation(summary = "查询所有定时任务", description = "获取系统中所有定时任务信息")
+ @SaCheckPermission("monitor:job:list")
+ @GetMapping("/sysJob/list")
+ public List list(SysJobVo vo) {
+ LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>();
+ wrapper.like(StringUtils.isNotBlank(vo.getJobName()), SysJob::getJobName, vo.getJobName());
+ wrapper.eq(StringUtils.isNotBlank(vo.getJobType()), SysJob::getJobType, vo.getJobType());
+ wrapper.eq(StringUtils.isNotBlank(vo.getJobStatus()), SysJob::getJobStatus, vo.getJobStatus());
+ wrapper.orderByDesc(SysJob::getCreateTime);
+ return sysJobService.list(wrapper);
+ }
+
+ /**
+ * @description [根据ID查询数据]
+ * @author Leocoder
+ */
+ @Operation(summary = "根据ID查询任务", description = "根据任务ID获取定时任务详细信息")
+ @SaCheckPermission("monitor:job:list")
+ @GetMapping("/sysJob/getById/{id}")
+ public SysJob getById(@PathVariable("id") Long id) {
+ return sysJobService.getById(id);
+ }
+
+ /**
+ * @description [删除任务]
+ * @author Leocoder
+ */
+ @Operation(summary = "删除定时任务", description = "根据任务ID删除指定的定时任务")
+ @SaCheckPermission("monitor:job:delete")
+ @PostMapping("/sysJob/deleteById/{id}")
+ public String deleteById(@PathVariable("id") Long id) {
+ try {
+ // 1、停止定时任务
+ CronUtil.remove(id + "");
+ // 2、进行删除
+ boolean remove = sysJobService.removeById(id);
+ if (!remove) {
+ return "删除任务失败,请重试";
+ }
+ return "删除成功";
+ } catch (Exception e) {
+ log.error("删除定时任务失败", e);
+ return "删除任务失败:" + e.getMessage();
+ }
+ }
+
+ /**
+ * @description [批量删除]
+ * @author Leocoder
+ */
+ @Operation(summary = "批量删除定时任务", description = "根据任务ID列表批量删除定时任务")
+ @SaCheckPermission("monitor:job:delete")
+ @Transactional(rollbackFor = Exception.class)
+ @PostMapping("/sysJob/batchDelete")
+ public String batchDelete(@RequestBody List jobIds) {
+ try {
+ // 1、停止定时任务
+ for (Long jobId : jobIds) {
+ CronUtil.remove(jobId + "");
+ }
+ // 2、批量删除
+ boolean batch = sysJobService.removeBatchByIds(jobIds);
+ if (!batch) {
+ return "删除任务失败,请重试";
+ }
+ return "批量删除成功";
+ } catch (Exception e) {
+ log.error("批量删除定时任务失败", e);
+ return "删除任务失败:" + e.getMessage();
+ }
+ }
+
+ /**
+ * @description [任务调度状态修改]
+ * @author Leocoder
+ */
+ @Operation(summary = "修改任务状态", description = "修改定时任务的运行状态和执行策略")
+ @SaCheckPermission("monitor:job:update")
+ @PostMapping("/sysJob/updateStatus/{id}/{jobStatus}/{policyStatus}")
+ public String updateStatus(@PathVariable("id") Long id,
+ @PathVariable("jobStatus") String jobStatus,
+ @PathVariable("policyStatus") String policyStatus) {
+ try {
+ sysJobService.updateStatus(id, jobStatus, policyStatus);
+ return "操作成功";
+ } catch (Exception e) {
+ log.error("修改任务状态失败", e);
+ return "操作失败:" + e.getMessage();
+ }
+ }
+
+ /**
+ * @description [立即运行任务-执行一次]
+ * @author Leocoder
+ */
+ @Operation(summary = "立即执行任务", description = "手动立即执行指定的定时任务")
+ @SaCheckPermission("monitor:job:run")
+ @GetMapping("/sysJob/runNow/{id}")
+ public String runNow(@PathVariable Long id) {
+ try {
+ sysJobService.runNow(id);
+ return "任务执行成功";
+ } catch (Exception e) {
+ log.error("立即执行任务失败", e);
+ return "任务执行失败:" + e.getMessage();
+ }
+ }
+
+ /**
+ * @description [添加定时任务]
+ * @author Leocoder
+ */
+ @Operation(summary = "新增定时任务", description = "创建新的定时任务配置")
+ @SaCheckPermission("monitor:job:add")
+ @PostMapping("/sysJob/add")
+ public String addJob(@RequestBody SysJob job) {
+ try {
+ sysJobService.addJob(job);
+ return "添加成功";
+ } catch (Exception e) {
+ log.error("添加定时任务失败", e);
+ return "添加失败:" + e.getMessage();
+ }
+ }
+
+ /**
+ * @description [修改定时任务]
+ * @author Leocoder
+ */
+ @Operation(summary = "修改定时任务", description = "更新现有定时任务的配置信息")
+ @SaCheckPermission("monitor:job:update")
+ @PostMapping("/sysJob/update")
+ public String updateJob(@RequestBody SysJob job) {
+ try {
+ sysJobService.updateJob(job);
+ return "修改成功";
+ } catch (Exception e) {
+ log.error("修改定时任务失败", e);
+ return "修改失败:" + e.getMessage();
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/coder-common-thin-plugins/coder-common-thin-job/src/main/java/org/leocoder/thin/job/service/SysJobService.java b/coder-common-thin-plugins/coder-common-thin-job/src/main/java/org/leocoder/thin/job/service/SysJobService.java
new file mode 100644
index 0000000..dffecdb
--- /dev/null
+++ b/coder-common-thin-plugins/coder-common-thin-job/src/main/java/org/leocoder/thin/job/service/SysJobService.java
@@ -0,0 +1,48 @@
+package org.leocoder.thin.job.service;
+
+import com.baomidou.mybatisplus.extension.service.IService;
+import org.leocoder.thin.domain.pojo.system.SysJob;
+
+/**
+ * @author Leocoder
+ * @description [定时任务服务接口]
+ */
+public interface SysJobService extends IService {
+
+ /**
+ * @description [停止任务]
+ * @author Leocoder
+ */
+ void pauseJob(Long id);
+
+ /**
+ * @description [启动定时任务]
+ * @author Leocoder
+ */
+ void resumeJob(Long id);
+
+ /**
+ * @description [任务调度状态修改]
+ * @author Leocoder
+ */
+ void updateStatus(Long id, String jobStatus, String policyStatus);
+
+ /**
+ * @description [立即运行任务-执行一次]
+ * @author Leocoder
+ */
+ void runNow(Long id);
+
+ /**
+ * @description [添加定时任务]
+ * @author Leocoder
+ */
+ void addJob(SysJob job);
+
+ /**
+ * @description [修改定时任务]
+ * @author Leocoder
+ */
+ void updateJob(SysJob job);
+
+}
\ No newline at end of file
diff --git a/coder-common-thin-plugins/coder-common-thin-job/src/main/java/org/leocoder/thin/job/service/impl/SysJobServiceImpl.java b/coder-common-thin-plugins/coder-common-thin-job/src/main/java/org/leocoder/thin/job/service/impl/SysJobServiceImpl.java
new file mode 100644
index 0000000..1b490e0
--- /dev/null
+++ b/coder-common-thin-plugins/coder-common-thin-job/src/main/java/org/leocoder/thin/job/service/impl/SysJobServiceImpl.java
@@ -0,0 +1,392 @@
+package org.leocoder.thin.job.service.impl;
+
+import cn.hutool.cron.CronUtil;
+import cn.hutool.extra.spring.SpringUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.lang3.ObjectUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.leocoder.thin.common.satoken.CoderLoginUtil;
+import org.leocoder.thin.domain.pojo.system.SysJob;
+import org.leocoder.thin.job.service.SysJobService;
+import org.leocoder.thin.job.task.CommonTimerTaskRunner;
+import org.leocoder.thin.mybatisplus.mapper.system.SysJobMapper;
+import org.springframework.scheduling.support.CronExpression;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+
+import java.lang.reflect.Method;
+
+/**
+ * @author Leocoder
+ * @description [定时任务服务实现类]
+ */
+@Slf4j
+@Service
+@RequiredArgsConstructor
+public class SysJobServiceImpl extends ServiceImpl implements SysJobService {
+
+ private final SysJobMapper sysJobMapper;
+
+ /**
+ * @description [停止任务]
+ * @author Leocoder
+ */
+ @Transactional(rollbackFor = Exception.class)
+ @Override
+ public void pauseJob(Long id) {
+ SysJob byId = this.getById(id);
+ if (byId == null) {
+ throw new RuntimeException("任务不存在");
+ }
+ if ("1".equals(byId.getJobStatus())) {
+ throw new RuntimeException("该任务已处于停止状态");
+ }
+
+ CronUtil.remove(id + "");
+
+ LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>();
+ updateWrapper.set(SysJob::getJobStatus, "1");
+ updateWrapper.eq(SysJob::getJobId, id);
+ boolean update = this.update(updateWrapper);
+
+ if (!update) {
+ throw new RuntimeException("暂停任务失败,请重试");
+ }
+ }
+
+ /**
+ * @description [启动定时任务]
+ * @author Leocoder
+ */
+ @Transactional(rollbackFor = Exception.class)
+ @Override
+ public void resumeJob(Long id) {
+ if (null == id) {
+ throw new RuntimeException("该任务未查询到");
+ }
+
+ SysJob job = this.getById(id);
+ if (job == null) {
+ throw new RuntimeException("任务不存在");
+ }
+
+ // 先停止定时任务
+ CronUtil.remove(id + "");
+
+ // 再注册定时任务
+ CronUtil.schedule(job.getJobId() + "", job.getCronExpression(), () -> {
+ executeMethod(job.getClassPath(), job.getMethodName(), job.getJobParams());
+ });
+
+ // 更新任务状态为正常
+ LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>();
+ updateWrapper.set(SysJob::getJobStatus, "0");
+ updateWrapper.eq(SysJob::getJobId, id);
+ this.update(updateWrapper);
+ }
+
+ /**
+ * @description [执行方法,多个参数传递,必须使用逗号分割,一个一个对应]
+ * @author Leocoder
+ */
+ private void executeMethod(String classPath, String methodName, String params) {
+ if (StringUtils.isBlank(classPath)) {
+ throw new RuntimeException("类绝对路径不能为空");
+ }
+ if (StringUtils.isBlank(methodName)) {
+ throw new RuntimeException("方法名称不能为空");
+ }
+
+ try {
+ // 使用SpringUtil获取类的实例
+ CommonTimerTaskRunner runner = (CommonTimerTaskRunner) SpringUtil.getBean(Class.forName(classPath));
+
+ Method[] methods = runner.getClass().getMethods();
+ Method targetMethod = null;
+
+ for (Method method : methods) {
+ if (method.getName().equals(methodName)) {
+ Class>[] parameterTypes = method.getParameterTypes();
+ if (parameterTypes.length > 0 && StringUtils.isBlank(params)) {
+ throw new IllegalArgumentException("缺少参数");
+ }
+ if (parameterTypes.length == 0 && StringUtils.isNotBlank(params)) {
+ throw new IllegalArgumentException("不应传递参数");
+ }
+ if (parameterTypes.length > 0 && StringUtils.isNotBlank(params)) {
+ String[] paramArray = params.split(",");
+ if (paramArray.length != parameterTypes.length) {
+ throw new IllegalArgumentException("参数数量不匹配");
+ }
+ for (int i = 0; i < paramArray.length; i++) {
+ Object convertedValue = convertToType(parameterTypes[i], paramArray[i]);
+ if (convertedValue == null) {
+ throw new IllegalArgumentException("参数类型不匹配");
+ }
+ }
+ targetMethod = method;
+ } else {
+ targetMethod = method;
+ }
+ break;
+ }
+ }
+
+ if (targetMethod != null) {
+ // 调用方法
+ if (StringUtils.isNotBlank(params)) {
+ targetMethod.invoke(runner, extractTypedParams(targetMethod.getParameterTypes(), params));
+ } else {
+ targetMethod.invoke(runner);
+ }
+ } else {
+ throw new IllegalArgumentException("指定方法不存在");
+ }
+ } catch (IllegalArgumentException e) {
+ throw e;
+ } catch (Exception e) {
+ log.error("执行定时任务出现异常", e);
+ throw new RuntimeException("执行定时任务出现异常:" + e.getMessage());
+ }
+ }
+
+ /**
+ * @description [多参数使用逗号分隔开]
+ * @author Leocoder
+ */
+ private Object[] extractTypedParams(Class>[] parameterTypes, String params) {
+ String[] paramArray = params.split(",");
+ Object[] typedParams = new Object[paramArray.length];
+ for (int i = 0; i < paramArray.length; i++) {
+ typedParams[i] = convertToType(parameterTypes[i], paramArray[i].trim());
+ }
+ return typedParams;
+ }
+
+ /**
+ * @description [类型转换]
+ * @author Leocoder
+ */
+ private Object convertToType(Class> targetType, String value) {
+ if (targetType.equals(String.class)) {
+ return value;
+ } else if (targetType.equals(Integer.class) || targetType.equals(int.class)) {
+ try {
+ return Integer.parseInt(value);
+ } catch (NumberFormatException ex) {
+ return null;
+ }
+ } else if (targetType.equals(Boolean.class) || targetType.equals(boolean.class)) {
+ return Boolean.parseBoolean(value);
+ } else if (targetType.equals(Long.class) || targetType.equals(long.class)) {
+ try {
+ return Long.parseLong(value);
+ } catch (NumberFormatException ex) {
+ return null;
+ }
+ }
+ // 根据需要添加更多类型转换
+ return null;
+ }
+
+ /**
+ * @description [任务调度状态修改]
+ * @author Leocoder
+ */
+ @Transactional(rollbackFor = Exception.class)
+ @Override
+ public void updateStatus(Long id, String jobStatus, String policyStatus) {
+ if (StringUtils.isBlank(jobStatus) || id == null) {
+ throw new RuntimeException("请传递相关信息");
+ }
+
+ if ("0".equals(jobStatus)) {
+ if ("1".equals(policyStatus)) {
+ resumeJob(id); // 启动定时任务
+ }
+ } else if ("1".equals(jobStatus)) {
+ pauseJob(id); // 停止定时任务
+ }
+
+ LambdaUpdateWrapper wrapper = new LambdaUpdateWrapper<>();
+ wrapper.set(SysJob::getJobStatus, jobStatus);
+ wrapper.set(SysJob::getPolicyStatus, policyStatus);
+ wrapper.eq(SysJob::getJobId, id);
+ boolean update = this.update(wrapper);
+
+ if (!update) {
+ throw new RuntimeException("操作失败,请重试");
+ }
+ }
+
+ /**
+ * @description [立即运行任务-执行一次,不影响定时调度]
+ * @author Leocoder
+ */
+ @Transactional(rollbackFor = Exception.class)
+ @Override
+ public void runNow(Long id) {
+ SysJob job = this.getById(id);
+ if (ObjectUtils.isEmpty(job) || job.getJobId() == null) {
+ throw new RuntimeException("未查到当前任务");
+ }
+
+ // 直接执行一次任务,不影响现有的定时调度
+ executeMethod(job.getClassPath(), job.getMethodName(), job.getJobParams());
+
+ // 注意:这里不移除定时任务,让定时调度继续按计划运行
+ log.info("立即执行任务完成,任务ID:{},定时调度继续保持运行", id);
+ }
+
+ /**
+ * @description [添加定时任务]
+ * @author Leocoder
+ */
+ @Transactional(rollbackFor = Exception.class)
+ @Override
+ public void addJob(SysJob job) {
+ // 1、参数校验
+ checkParams(job);
+
+ // 2、是否添加重复的定时任务
+ LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>();
+ wrapper.eq(SysJob::getClassPath, job.getClassPath());
+ wrapper.eq(SysJob::getMethodName, job.getMethodName());
+ wrapper.eq(SysJob::getCronExpression, job.getCronExpression());
+ long count = this.count(wrapper);
+ if (count > 0) {
+ throw new RuntimeException("存在重复执行的定时任务,名称为:" + job.getJobName());
+ }
+
+ // 3、添加定时任务
+ boolean save = this.save(job);
+ if (!save) {
+ throw new RuntimeException("添加失败,请重试");
+ }
+
+ // 4、根据任务状态,进行执行定时策略
+ if ("0".equals(job.getJobStatus())) {
+ if ("1".equals(job.getPolicyStatus())) {
+ // 开启定时任务
+ resumeJob(job.getJobId());
+ } else if ("2".equals(job.getPolicyStatus())) {
+ // 先停止定时任务
+ CronUtil.remove(job.getJobId() + "");
+ // 执行一次
+ executeMethod(job.getClassPath(), job.getMethodName(), job.getJobParams());
+ } else {
+ // 停止任务
+ CronUtil.remove(job.getJobId() + "");
+ LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>();
+ updateWrapper.set(SysJob::getPolicyStatus, "3");
+ updateWrapper.eq(SysJob::getJobId, job.getJobId());
+ boolean update = this.update(updateWrapper);
+ if (!update) {
+ throw new RuntimeException("操作失败,请重试");
+ }
+ }
+ } else {
+ // 停止任务,计划策略并改为放弃执行
+ CronUtil.remove(job.getJobId() + "");
+ LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>();
+ updateWrapper.set(SysJob::getPolicyStatus, "3");
+ updateWrapper.eq(SysJob::getJobId, job.getJobId());
+ boolean update = this.update(updateWrapper);
+ if (!update) {
+ throw new RuntimeException("操作失败,请重试");
+ }
+ }
+ }
+
+ /**
+ * @description [参数校验]
+ * @author Leocoder
+ */
+ private void checkParams(SysJob job) {
+ // 校验表达式
+ if (!CronExpression.isValidExpression(job.getCronExpression())) {
+ throw new RuntimeException("cron表达式:" + job.getCronExpression() + "格式不正确");
+ }
+
+ // 校验定时任务类
+ try {
+ Class> actionClass = Class.forName(job.getClassPath());
+ if (!CommonTimerTaskRunner.class.isAssignableFrom(actionClass)) {
+ throw new RuntimeException("定时任务对应的类:" + job.getClassPath() + "不符合要求");
+ }
+ } catch (ClassNotFoundException e) {
+ throw new RuntimeException("定时任务找不到对应的类,名称为:" + job.getClassPath());
+ }
+ }
+
+ /**
+ * @description [修改定时任务]
+ * @author Leocoder
+ */
+ @Transactional(rollbackFor = Exception.class)
+ @Override
+ public void updateJob(SysJob job) {
+ // 1、参数校验
+ checkParams(job);
+ if (job.getJobId() == null) {
+ throw new RuntimeException("请选择需要修改的任务");
+ }
+
+ // 2、是否修改为数据库已经存在的定时任务(保持与添加任务相同的检查逻辑)
+ LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>();
+ wrapper.eq(SysJob::getClassPath, job.getClassPath());
+ wrapper.eq(SysJob::getMethodName, job.getMethodName());
+ wrapper.eq(SysJob::getCronExpression, job.getCronExpression());
+ wrapper.ne(SysJob::getJobId, job.getJobId()); // 排除当前任务
+ long count = this.count(wrapper);
+ if (count > 0) {
+ throw new RuntimeException("存在重复执行的定时任务,名称为:" + job.getJobName());
+ }
+
+ // 3、修改任务
+ // 这里应该从登录用户上下文获取
+ job.setUpdateBy(CoderLoginUtil.getLoginName());
+ boolean update = this.updateById(job);
+ if (!update) {
+ throw new RuntimeException("修改失败,请重试");
+ }
+
+ // 4、根据任务状态,进行执行定时策略
+ if ("0".equals(job.getJobStatus())) {
+ if ("1".equals(job.getPolicyStatus())) {
+ // 先停止定时任务,再开启定时任务
+ resumeJob(job.getJobId());
+ } else if ("2".equals(job.getPolicyStatus())) {
+ // 先停止定时任务
+ CronUtil.remove(job.getJobId() + "");
+ // 再开始执行一次定时任务
+ executeMethod(job.getClassPath(), job.getMethodName(), job.getJobParams());
+ } else {
+ // 停止任务,计划策略并改为放弃执行
+ CronUtil.remove(job.getJobId() + "");
+ LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>();
+ updateWrapper.set(SysJob::getPolicyStatus, "3");
+ updateWrapper.eq(SysJob::getJobId, job.getJobId());
+ boolean updateBoolean = this.update(updateWrapper);
+ if (!updateBoolean) {
+ throw new RuntimeException("操作失败,请重试");
+ }
+ }
+ } else {
+ // 停止任务,计划策略并改为放弃执行
+ CronUtil.remove(job.getJobId() + "");
+ LambdaUpdateWrapper updateWrapper = new LambdaUpdateWrapper<>();
+ updateWrapper.set(SysJob::getPolicyStatus, "3");
+ updateWrapper.eq(SysJob::getJobId, job.getJobId());
+ boolean updateBoolean = this.update(updateWrapper);
+ if (!updateBoolean) {
+ throw new RuntimeException("操作失败,请重试");
+ }
+ }
+ }
+
+}
\ No newline at end of file
diff --git a/coder-common-thin-plugins/coder-common-thin-job/src/main/java/org/leocoder/thin/job/task/CoderJobListener.java b/coder-common-thin-plugins/coder-common-thin-job/src/main/java/org/leocoder/thin/job/task/CoderJobListener.java
new file mode 100644
index 0000000..af2f521
--- /dev/null
+++ b/coder-common-thin-plugins/coder-common-thin-job/src/main/java/org/leocoder/thin/job/task/CoderJobListener.java
@@ -0,0 +1,67 @@
+package org.leocoder.thin.job.task;
+
+import cn.hutool.cron.CronUtil;
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
+import jakarta.annotation.Resource;
+import lombok.extern.slf4j.Slf4j;
+import org.apache.commons.collections4.CollectionUtils;
+import org.leocoder.thin.domain.pojo.system.SysJob;
+import org.leocoder.thin.job.service.SysJobService;
+import org.springframework.boot.context.event.ApplicationStartedEvent;
+import org.springframework.context.ApplicationListener;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.Ordered;
+
+import java.util.List;
+
+/**
+ * @author Leocoder
+ * @description [定时任务监听器,系统启动时将定时任务启动]
+ */
+@Slf4j
+@Configuration
+public class CoderJobListener implements ApplicationListener, Ordered {
+
+ @Resource
+ private SysJobService sysJobService;
+
+ @SuppressWarnings("ALL")
+ @Override
+ public void onApplicationEvent(ApplicationStartedEvent applicationStartedEvent) {
+ try {
+ // 查询状态正常的定时任务 AND 执行策略是立即执行类型的数据,筛选后进行开始定时任务
+ LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>();
+ wrapper.eq(SysJob::getJobStatus, "0"); // 正常状态
+ wrapper.eq(SysJob::getPolicyStatus, "1"); // 立即执行
+
+ List jobList = sysJobService.list(wrapper);
+ if(CollectionUtils.isNotEmpty(jobList)){
+ for (SysJob sysJob : jobList) {
+ try {
+ // 启动定时任务
+ sysJobService.resumeJob(sysJob.getJobId());
+ log.info("自动启动定时任务:{},表达式:{}", sysJob.getJobName(), sysJob.getCronExpression());
+ } catch (Exception e) {
+ log.error("自动启动定时任务失败:{},错误:{}", sysJob.getJobName(), e.getMessage());
+ }
+ }
+ }
+
+ // 设置秒级别的启用
+ CronUtil.setMatchSecond(true);
+ log.info("启动定时器执行器,支持秒级精度");
+ CronUtil.restart();
+
+ log.info("定时任务系统初始化完成,共启动{}个任务", jobList != null ? jobList.size() : 0);
+
+ } catch (Exception e) {
+ log.error("定时任务系统初始化失败", e);
+ }
+ }
+
+ @Override
+ public int getOrder() {
+ return LOWEST_PRECEDENCE;
+ }
+
+}
\ No newline at end of file
diff --git a/coder-common-thin-plugins/coder-common-thin-job/src/main/java/org/leocoder/thin/job/task/CoderJobTimerTaskRunner.java b/coder-common-thin-plugins/coder-common-thin-job/src/main/java/org/leocoder/thin/job/task/CoderJobTimerTaskRunner.java
new file mode 100644
index 0000000..3c8a46a
--- /dev/null
+++ b/coder-common-thin-plugins/coder-common-thin-job/src/main/java/org/leocoder/thin/job/task/CoderJobTimerTaskRunner.java
@@ -0,0 +1,55 @@
+package org.leocoder.thin.job.task;
+
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
+import org.leocoder.thin.job.service.SysJobService;
+import org.springframework.stereotype.Component;
+
+/**
+ * @author Leocoder
+ * @description [定时任务执行器示例实现]
+ * 注意:仅支持字符串、数字、布尔值进行传递参数。
+ */
+@Slf4j
+@Component
+@RequiredArgsConstructor
+public class CoderJobTimerTaskRunner implements CommonTimerTaskRunner {
+
+ private final SysJobService sysJobService;
+
+ /**
+ * @description [单个参数,param可以传递参数,通过数据库的job_params]
+ * @author Leocoder
+ */
+ @Override
+ public void paramAction(String param) {
+ log.info("执行单参数定时任务,参数:{}", param);
+ // 这里可以执行具体的业务逻辑
+ // 示例:发送邮件、清理缓存、同步数据等
+ }
+
+ /**
+ * @description [无参数执行]
+ * @author Leocoder
+ */
+ @Override
+ public void noParamAction() {
+ log.info("执行无参数定时任务");
+ // 示例:查询所有定时任务并输出
+ sysJobService.list().forEach(job ->
+ log.info("任务:{},状态:{}", job.getJobName(), job.getJobStatusText())
+ );
+ }
+
+ /**
+ * @description [多参数执行]
+ * @author Leocoder
+ */
+ @Override
+ public void manyParamsAction(String param1, Integer param2) {
+ log.info("执行多参数定时任务,参数1:{},参数2:{}", param1, param2);
+ // 这里可以执行具体的业务逻辑
+ // 示例:根据参数处理不同类型的数据
+ }
+
+}
\ No newline at end of file
diff --git a/coder-common-thin-plugins/coder-common-thin-job/src/main/java/org/leocoder/thin/job/task/CommonTimerTaskRunner.java b/coder-common-thin-plugins/coder-common-thin-job/src/main/java/org/leocoder/thin/job/task/CommonTimerTaskRunner.java
new file mode 100644
index 0000000..29f5bd0
--- /dev/null
+++ b/coder-common-thin-plugins/coder-common-thin-job/src/main/java/org/leocoder/thin/job/task/CommonTimerTaskRunner.java
@@ -0,0 +1,27 @@
+package org.leocoder.thin.job.task;
+
+/**
+ * @author Leocoder
+ * @description [定时任务执行器通用接口 - 所有定时任务都实现这个接口,方便我们遍历所有可用的定时任务类]
+ */
+public interface CommonTimerTaskRunner {
+
+ /**
+ * @description [单参数执行]
+ * @author Leocoder
+ */
+ void paramAction(String param);
+
+ /**
+ * @description [无参数执行]
+ * @author Leocoder
+ */
+ void noParamAction();
+
+ /**
+ * @description [多参数执行,多个参数传递,必须使用逗号分割,一个一个对应]
+ * @author Leocoder
+ */
+ void manyParamsAction(String param1, Integer param2);
+
+}
\ No newline at end of file
diff --git a/sql/20250926-sys_menu_dict_buttons.sql b/sql/20250926-sys_menu_dict_buttons.sql
deleted file mode 100644
index aab2930..0000000
--- a/sql/20250926-sys_menu_dict_buttons.sql
+++ /dev/null
@@ -1,26 +0,0 @@
--- 字典管理按钮权限补充数据
--- 时间:2025-09-26
--- 功能:为字典管理添加操作按钮和子菜单
-
--- 为字典管理主菜单(ID:24)添加操作按钮
--- 搜索按钮
-INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `en_name`, `parent_id`, `menu_type`, `path`, `name`, `component`, `icon`, `auth`, `menu_status`, `active_menu`, `is_hide`, `is_link`, `is_keep_alive`, `is_full`, `is_affix`, `is_spread`, `sorted`, `create_by`, `create_time`, `update_by`, `update_time`)
-VALUES (25, '搜索', NULL, 24, '3', '', '', '', '', 'system:dict:search', '0', '', '0', '', '0', '1', '1', '1', 1, 'admin', NOW(), 'admin', NOW());
-
--- 新增按钮
-INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `en_name`, `parent_id`, `menu_type`, `path`, `name`, `component`, `icon`, `auth`, `menu_status`, `active_menu`, `is_hide`, `is_link`, `is_keep_alive`, `is_full`, `is_affix`, `is_spread`, `sorted`, `create_by`, `create_time`, `update_by`, `update_time`)
-VALUES (26, '新增', NULL, 24, '3', '', '', '', '', 'system:dict:add', '0', '', '0', '', '0', '1', '1', '1', 2, 'admin', NOW(), 'admin', NOW());
-
--- 修改按钮
-INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `en_name`, `parent_id`, `menu_type`, `path`, `name`, `component`, `icon`, `auth`, `menu_status`, `active_menu`, `is_hide`, `is_link`, `is_keep_alive`, `is_full`, `is_affix`, `is_spread`, `sorted`, `create_by`, `create_time`, `update_by`, `update_time`)
-VALUES (27, '修改', NULL, 24, '3', '', '', '', '', 'system:dict:update', '0', '', '0', '', '0', '1', '1', '1', 3, 'admin', NOW(), 'admin', NOW());
-
--- 删除按钮
-INSERT INTO `sys_menu` (`menu_id`, `menu_name`, `en_name`, `parent_id`, `menu_type`, `path`, `name`, `component`, `icon`, `auth`, `menu_status`, `active_menu`, `is_hide`, `is_link`, `is_keep_alive`, `is_full`, `is_affix`, `is_spread`, `sorted`, `create_by`, `create_time`, `update_by`, `update_time`)
-VALUES (28, '删除', NULL, 24, '3', '', '', '', '', 'system:dict:delete', '0', '', '0', '', '0', '1', '1', '1', 4, 'admin', NOW(), 'admin', NOW());
-
--- 为超级管理员角色添加这些新权限(假设角色ID为1)
-INSERT INTO `sys_role_menu` VALUES (1, 25);
-INSERT INTO `sys_role_menu` VALUES (1, 26);
-INSERT INTO `sys_role_menu` VALUES (1, 27);
-INSERT INTO `sys_role_menu` VALUES (1, 28);
\ No newline at end of file