Compare commits

..

No commits in common. "ae32e6d95399127e578fdb3dfff1db89466e50fa" and "9b8703e192b4bf35d25411f7396530e756c2e678" have entirely different histories.

21 changed files with 32 additions and 1391 deletions

View File

@ -5,7 +5,6 @@
<option name="apiProjectIds">
<array>
<option value="&lt;byte-array&gt;rO0ABXNyADZjb20uaXRhbmdjZW50LmlkZWEucGx1Z2luLmFwaS5hY2NvdW50LlByb2plY3RBbmRNb2R1bGUAAAAAAAAAAQIAFVoABmVuYWJsZUwACG1vZHVsZUlkdAASTGphdmEvbGFuZy9TdHJpbmc7TAAGb3RoZXIxcQB+AAFMAAdvdGhlcjEwcQB+AAFMAAdvdGhlcjExcQB+AAFMAAdvdGhlcjEycQB+AAFMAAZvdGhlcjJxAH4AAUwABm90aGVyM3EAfgABTAAGb3RoZXI0cQB+AAFMAAZvdGhlcjVxAH4AAUwABm90aGVyNnEAfgABTAAGb3RoZXI3cQB+AAFMAAZvdGhlcjhxAH4AAUwABm90aGVyOXEAfgABTAAKcGF0aEJlZm9yZXEAfgABTAANcHJvamVjdEZvbGRlcnEAfgABTAAPcHJvamVjdEZvbGRlcklkcQB+AAFMAAlwcm9qZWN0SWRxAH4AAUwAC3Byb2plY3ROYW1lcQB+AAFMAAxzY2hlbWFGb2xkZXJxAH4AAUwACHNjaGVtYUlkcQB+AAF4cAF0ABhjb2Rlci1jb21tb24tdGhpbi1zeXN0ZW10AAc2ODg2NjExcHBwdAAHNjIzOTkwNnQAC2JyYW5jaC1tYWludAAM6buY6K6k5qih5Z2XcHBwcHB0AAB0AAnmoLnnm67lvZV0AAEwdAAHNzE2MjUxMHQAEWNvZGVyLWNvbW1vbi10aGlucQB+AAlxAH4ACg==&lt;/byte-array&gt;" />
<option value="&lt;byte-array&gt;rO0ABXNyADZjb20uaXRhbmdjZW50LmlkZWEucGx1Z2luLmFwaS5hY2NvdW50LlByb2plY3RBbmRNb2R1bGUAAAAAAAAAAQIAFVoABmVuYWJsZUwACG1vZHVsZUlkdAASTGphdmEvbGFuZy9TdHJpbmc7TAAGb3RoZXIxcQB+AAFMAAdvdGhlcjEwcQB+AAFMAAdvdGhlcjExcQB+AAFMAAdvdGhlcjEycQB+AAFMAAZvdGhlcjJxAH4AAUwABm90aGVyM3EAfgABTAAGb3RoZXI0cQB+AAFMAAZvdGhlcjVxAH4AAUwABm90aGVyNnEAfgABTAAGb3RoZXI3cQB+AAFMAAZvdGhlcjhxAH4AAUwABm90aGVyOXEAfgABTAAKcGF0aEJlZm9yZXEAfgABTAANcHJvamVjdEZvbGRlcnEAfgABTAAPcHJvamVjdEZvbGRlcklkcQB+AAFMAAlwcm9qZWN0SWRxAH4AAUwAC3Byb2plY3ROYW1lcQB+AAFMAAxzY2hlbWFGb2xkZXJxAH4AAUwACHNjaGVtYUlkcQB+AAF4cAF0ABVjb2Rlci1jb21tb24tdGhpbi1qb2J0AAc2ODg2NjExcHBwdAAHNjIzOTkwNnQAC2JyYW5jaC1tYWludAAM6buY6K6k5qih5Z2XcHBwcHB0AAB0AAnmoLnnm67lvZV0AAEwdAAHNzE2MjUxMHQAEWNvZGVyLWNvbW1vbi10aGlucQB+AAlxAH4ACg==&lt;/byte-array&gt;" />
</array>
</option>
<option name="treeNodes" value="&lt;byte-array&gt;rO0ABXNyABdqYXZhLnV0aWwuTGlua2VkSGFzaE1hcDTATlwQbMD7AgABWgALYWNjZXNzT3JkZXJ4cgARamF2YS51dGlsLkhhc2hNYXAFB9rBwxZg0QMAAkYACmxvYWRGYWN0b3JJAAl0aHJlc2hvbGR4cD9AAAAAAAAMdwgAAAAQAAAABHQABzEyOTMxODlzcgAuY29tLml0YW5nY2VudC5pZGVhLnBsdWdpbi5hcGkuYWNjb3VudC5UcmVlTm9kZQAAAAAAAAABAgAQTAAHYWxsUGF0aHQAEkxqYXZhL2xhbmcvU3RyaW5nO1sAFGJyYW5jaEFuZFZlcnNpb25JdGVtdABLW0xjb20vaXRhbmdjZW50L2lkZWEvcGx1Z2luL2RpYWxvZy9jb21wb25lbnQvYWNjb3VudC9BY2NvdW50UmlnaHRQYW5lbEl0ZW07TAAUYnJhbmNoSWRBbmRWZXJzaW9uSWRxAH4ABUwACGNoaWxkcmVudAAPTGphdmEvdXRpbC9NYXA7TAAKZm9sZGVyVHlwZXEAfgAFTAAIZnVsbFBhdGhxAH4ABUwAA2tleXEAfgAFWwAJbW9kZWxJdGVtcQB+AAZMAAhtb2R1bGVJZHEAfgAFTAAEbmFtZXEAfgAFTAAIcGFyZW50SWRxAH4ABUwACXByb2plY3RJZHEAfgAFTAALcHJvamVjdE5hbWVxAH4ABUwABnRlYW1JZHEAfgAFTAAIdGVhbU5hbWVxAH4ABUwABHR5cGV0ADBMY29tL2l0YW5nY2VudC9pZGVhL3BsdWdpbi9hcGkvYWNjb3VudC9Ob2RlVHlwZTt4cHQADOS4quS6uuepuumXtHBwc3EAfgAAP0AAAAAAAAx3CAAAABAAAAAGdAAHNDg1MTE4NnNxAH4ABHQAFOS4quS6uuepuumXtC9jb2RlaHVicHBzcQB+AAA/QAAAAAAAAHcIAAAAEAAAAAB4AHBwcQB+AAxwcHQAEWNvZGVodWIgKDQ4NTExODYpdAAHMTI5MzE4OXEAfgAMdAAHY29kZWh1YnEAfgARcH5yAC5jb20uaXRhbmdjZW50LmlkZWEucGx1Z2luLmFwaS5hY2NvdW50Lk5vZGVUeXBlAAAAAAAAAAASAAB4cgAOamF2YS5sYW5nLkVudW0AAAAAAAAAABIAAHhwdAAHUFJPSkVDVHQABzUwMjI4NDZzcQB+AAR0ACXkuKrkurrnqbrpl7QvU2ltcGxlU3ByaW5nQm9vdFRlbXBsYXRlcHBzcQB+AAA/QAAAAAAAAHcIAAAAEAAAAAB4AHBwcQB+ABdwcHQAIlNpbXBsZVNwcmluZ0Jvb3RUZW1wbGF0ZSAoNTAyMjg0Nil0AAcxMjkzMTg5cQB+ABd0ABhTaW1wbGVTcHJpbmdCb290VGVtcGxhdGVxAH4AHHBxAH4AFXQABzY0OTE0ODBzcQB+AAR0ABfkuKrkurrnqbrpl7Qvcy1wYXktbWFsbHBwc3EAfgAAP0AAAAAAAAB3CAAAABAAAAAAeABwcHEAfgAecHB0ABRzLXBheS1tYWxsICg2NDkxNDgwKXQABzEyOTMxODlxAH4AHnQACnMtcGF5LW1hbGxxAH4AI3BxAH4AFXQABzY1MTkxNzdzcQB+AAR0ABzkuKrkurrnqbrpl7QvY29kZXItc2hvcnRsaW5rcHBzcQB+AAA/QAAAAAAAAHcIAAAAEAAAAAB4AHBwcQB+ACVwcHQAGWNvZGVyLXNob3J0bGluayAoNjUxOTE3Nyl0AAcxMjkzMTg5cQB+ACV0AA9jb2Rlci1zaG9ydGxpbmtxAH4AKnBxAH4AFXQABzY5Mjk1NjNzcQB+AAR0ABnkuKrkurrnqbrpl7QvY29kZXItaGlib29rcHBzcQB+AAA/QAAAAAAAAHcIAAAAEAAAAAB4AHBwcQB+ACxwcHQAFmNvZGVyLWhpYm9vayAoNjkyOTU2Myl0AAcxMjkzMTg5cQB+ACx0AAxjb2Rlci1oaWJvb2txAH4AMXBxAH4AFXQABzcxNjI1MTBzcQB+AAR0AB7kuKrkurrnqbrpl7QvY29kZXItY29tbW9uLXRoaW5wcHNxAH4AAD9AAAAAAAAAdwgAAAAQAAAAAHgAcHBxAH4AM3BwdAAbY29kZXItY29tbW9uLXRoaW4gKDcxNjI1MTApdAAHMTI5MzE4OXEAfgAzdAARY29kZXItY29tbW9uLXRoaW5xAH4AOHBxAH4AFXgAcHBxAH4AA3BwcQB+AApwcHBxAH4AA3EAfgAKfnEAfgATdAAEVEVBTXQABzIyNDA3MzdzcQB+AAR0ABLmtYvor5XnlKjkvovpobnnm65wcHNxAH4AAD9AAAAAAAAAdwgAAAAQAAAAAHgAcHBxAH4APHBwcQB+AD5wcHBxAH4APHEAfgA+cQB+ADp0AAcyMjQwNzQ0c3EAfgAEdAAJTGVv5rWL6K+VcHBzcQB+AAA/QAAAAAAADHcIAAAAEAAAAAF0AAczODU3NDk4c3EAfgAEdAAcTGVv5rWL6K+VL0FwaWZveOeugOWNlea1i+ivlXBwc3EAfgAAP0AAAAAAAAB3CAAAABAAAAAAeABwcHEAfgBEcHB0ABxBcGlmb3jnroDljZXmtYvor5UgKDM4NTc0OTgpdAAHMjI0MDc0NHEAfgBEdAASQXBpZm94566A5Y2V5rWL6K+VcQB+AElwcQB+ABV4AHBwcQB+AEBwcHEAfgBCcHBwcQB+AEBxAH4AQnEAfgA6dAAHMzU3NjE2NXNxAH4ABHQABuWFrOWPuHBwc3EAfgAAP0AAAAAAAAx3CAAAABAAAAABdAAHNjU3MDQzM3NxAH4ABHQAE+WFrOWPuC/lhbbku5blhaXlupNwcHNxAH4AAD9AAAAAAAAAdwgAAAAQAAAAAHgAcHBxAH4AT3BwdAAW5YW25LuW5YWl5bqTICg2NTcwNDMzKXQABzM1NzYxNjVxAH4AT3QADOWFtuS7luWFpeW6k3EAfgBUcHEAfgAVeABwcHEAfgBLcHBxAH4ATXBwcHEAfgBLcQB+AE1xAH4AOngA&lt;/byte-array&gt;" />

View File

@ -17,8 +17,6 @@
<file url="file://$PROJECT_DIR$/coder-common-thin-plugins/coder-common-thin-dict/src/main/resources" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/coder-common-thin-plugins/coder-common-thin-easyexcel/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/coder-common-thin-plugins/coder-common-thin-easyexcel/src/main/resources" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/coder-common-thin-plugins/coder-common-thin-job/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/coder-common-thin-plugins/coder-common-thin-job/src/main/resources" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/coder-common-thin-plugins/coder-common-thin-limit/src/main/java" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/coder-common-thin-plugins/coder-common-thin-limit/src/main/resources" charset="UTF-8" />
<file url="file://$PROJECT_DIR$/coder-common-thin-plugins/coder-common-thin-oper-logs/src/main/java" charset="UTF-8" />

View File

@ -206,44 +206,6 @@ mvn test -pl coder-common-thin-web
- 严禁在Sa-Token配置中添加业务接口的排除路径
- 严禁通过其他方式绕过权限验证
5. **Controller接口文档规范**: 所有Controller必须配置Swagger注解以生成规范的中文接口文档
- **强制要求**: 每个Controller类和方法都必须添加完整的Swagger注解
- **类级别注解**: 必须添加`@Tag(name = "中文模块名", description = "详细描述")`
- **方法级别注解**: 必须添加`@Operation(summary = "中文接口名", description = "详细功能描述")`
- **必要导入**: 在Controller类中导入
```java
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
```
- **依赖配置**: 使用Swagger注解的模块必须在pom.xml中添加SpringDoc依赖
```xml
<!-- SpringDoc OpenAPI 3.0 -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
</dependency>
```
- **标准示例**:
```java
@Tag(name = "定时任务管理", description = "系统定时任务的增删改查和执行控制")
@RestController
@RequestMapping("/coder")
public class SysJobController {
@Operation(summary = "分页查询定时任务", description = "根据查询条件分页获取系统定时任务列表")
@SaCheckPermission("monitor:job:list")
@GetMapping("/sysJob/listPage")
public IPage<SysJob> listPage(SysJobVo vo) {
// 业务逻辑
}
}
```
- **接口文档效果**: 正确配置后,接口文档将显示中文模块名和接口名,而不是英文类名和方法名
- **禁止行为**:
- 严禁创建没有Swagger注解的Controller
- 严禁使用英文summary和description
- 严禁在缺少SpringDoc依赖的模块中使用Swagger注解
### 工具类使用
项目提供了丰富的工具类,位于`coder-common-thin-common/utils`
- **缓存工具**: `RedisUtil`, `LocalCacheUtil`
@ -303,18 +265,3 @@ mvn test -pl coder-common-thin-web
- **禁止行为**: 严禁在代码修改完成后自动运行编译命令
- **原因说明**: 编译操作应由开发者根据实际需要手动执行,避免不必要的编译开销和潜在的环境冲突
- **允许行为**: 仅在用户明确要求时才执行编译操作
7. **Swagger注解强制要求**: 所有Controller必须配置完整的Swagger注解确保接口文档显示中文
- **强制配置**: 新建Controller时必须同时添加`@Tag`和`@Operation`注解
- **依赖检查**: 使用Swagger注解前必须确保模块pom.xml包含SpringDoc依赖
- **中文规范**: 所有summary和description必须使用中文描述
- **编译验证**: 添加Swagger注解后必须确保项目能正常编译如遇到"Cannot resolve symbol"错误,立即检查依赖配置
- **示例配置**:
```xml
<!-- 在模块pom.xml中添加 -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
</dependency>
```
- **验证标准**: 接口文档中必须显示中文模块名和接口名,而非英文类名和方法名

View File

@ -1,73 +0,0 @@
package org.leocoder.thin.domain.model.bo.system;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.io.Serializable;
/**
* @author Leocoder
* @description [定时任务业务对象]
*/
@Data
public class SysJobBo implements Serializable {
/**
* 任务ID
*/
private Long jobId;
/**
* 任务名称
*/
@NotBlank(message = "任务名称不能为空")
private String jobName;
/**
* 任务类型[1-管理平台 2-小程序 3-App]
*/
@NotBlank(message = "任务类型不能为空")
private String jobType;
/**
* 类路径
*/
@NotBlank(message = "类路径不能为空")
private String classPath;
/**
* 方法名称
*/
@NotBlank(message = "方法名称不能为空")
private String methodName;
/**
* cron执行表达式
*/
@NotBlank(message = "cron执行表达式不能为空")
private String cronExpression;
/**
* cron计划策略[1-立即执行 2-执行一次 3-放弃执行]
*/
@NotBlank(message = "cron计划策略不能为空")
private String policyStatus;
/**
* 任务状态 [0正常 1暂停]
*/
@NotBlank(message = "任务状态不能为空")
private String jobStatus;
/**
* 任务参数
*/
private String jobParams;
/**
* 任务备注
*/
private String remark;
}

View File

@ -1,65 +0,0 @@
package org.leocoder.thin.domain.model.vo.system;
import org.leocoder.thin.domain.model.vo.base.BaseVo;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* @author Leocoder
* @description [定时任务查询视图对象]
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class SysJobVo extends BaseVo {
/**
* 主键ID
*/
private Long jobId;
/**
* 任务名称
*/
private String jobName;
/**
* 任务类型[1-管理平台 2-小程序 3-App]
*/
private String jobType;
/**
* cron执行表达式
*/
private String cronExpression;
/**
* cron计划策略[1-立即执行 2-执行一次 3-放弃执行]
*/
private String policyStatus;
/**
* 任务状态 [0正常 1暂停]
*/
private String jobStatus;
/**
* 任务参数
*/
private String jobParams;
/**
* 类路径
*/
private String classPath;
/**
* 方法名称
*/
private String methodName;
/**
* 参数备注
*/
private String remark;
}

View File

@ -1,149 +0,0 @@
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 "其他";
}
}
}

View File

@ -1,14 +0,0 @@
package org.leocoder.thin.mybatisplus.mapper.system;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import org.leocoder.thin.domain.pojo.system.SysJob;
/**
* @author Leocoder
* @description [定时任务数据访问层]
*/
@Mapper
public interface SysJobMapper extends BaseMapper<SysJob> {
}

View File

@ -1,85 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.leocoder.thin</groupId>
<artifactId>coder-common-thin-plugins</artifactId>
<version>${revision}</version>
</parent>
<artifactId>coder-common-thin-job</artifactId>
<description>定时任务插件模块</description>
<dependencies>
<!-- Spring Boot Starter Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Boot Starter Validation -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- MyBatis Plus -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-spring-boot3-starter</artifactId>
</dependency>
<!-- Sa-Token 权限认证 -->
<dependency>
<groupId>cn.dev33</groupId>
<artifactId>sa-token-spring-boot3-starter</artifactId>
</dependency>
<!-- Hutool 工具类 -->
<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<!-- Apache Commons Lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
<!-- SpringDoc OpenAPI 3.0 -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- 项目内部依赖 -->
<dependency>
<groupId>org.leocoder.thin</groupId>
<artifactId>coder-common-thin-common</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>org.leocoder.thin</groupId>
<artifactId>coder-common-thin-model</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>org.leocoder.thin</groupId>
<artifactId>coder-common-thin-mybatisplus</artifactId>
<version>${revision}</version>
</dependency>
</dependencies>
</project>

View File

@ -1,19 +0,0 @@
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 {
}

View File

@ -1,20 +0,0 @@
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("定时任务插件已启用");
}
}

View File

@ -1,201 +0,0 @@
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<SysJob> listPage(SysJobVo vo) {
// 分页构造器
Page<SysJob> page = new Page<>(vo.getPageNo(), vo.getPageSize());
// 条件构造器
LambdaQueryWrapper<SysJob> 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<SysJob> list(SysJobVo vo) {
LambdaQueryWrapper<SysJob> 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<Long> 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();
}
}
}

View File

@ -1,48 +0,0 @@
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<SysJob> {
/**
* @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);
}

View File

@ -1,392 +0,0 @@
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<SysJobMapper, SysJob> 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<SysJob> 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<SysJob> 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<SysJob> 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<SysJob> 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<SysJob> 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<SysJob> 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<SysJob> 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<SysJob> 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<SysJob> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.set(SysJob::getPolicyStatus, "3");
updateWrapper.eq(SysJob::getJobId, job.getJobId());
boolean updateBoolean = this.update(updateWrapper);
if (!updateBoolean) {
throw new RuntimeException("操作失败,请重试");
}
}
}
}

View File

@ -1,67 +0,0 @@
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<ApplicationStartedEvent>, Ordered {
@Resource
private SysJobService sysJobService;
@SuppressWarnings("ALL")
@Override
public void onApplicationEvent(ApplicationStartedEvent applicationStartedEvent) {
try {
// 查询状态正常的定时任务 AND 执行策略是立即执行类型的数据筛选后进行开始定时任务
LambdaQueryWrapper<SysJob> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(SysJob::getJobStatus, "0"); // 正常状态
wrapper.eq(SysJob::getPolicyStatus, "1"); // 立即执行
List<SysJob> 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;
}
}

View File

@ -1,55 +0,0 @@
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);
// 这里可以执行具体的业务逻辑
// 示例根据参数处理不同类型的数据
}
}

View File

@ -1,27 +0,0 @@
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);
}

View File

@ -24,7 +24,6 @@
<module>coder-common-thin-oper-logs</module>
<module>coder-common-thin-oss</module>
<module>coder-common-thin-dict</module>
<module>coder-common-thin-job</module>
</modules>
</project>

View File

@ -44,11 +44,11 @@
<version>${revision}</version>
</dependency>
<!-- 定时任务 -->
<dependency>
<groupId>org.leocoder.thin</groupId>
<artifactId>coder-common-thin-job</artifactId>
<version>${revision}</version>
</dependency>
<!-- <dependency> -->
<!-- <groupId>org.leocoder.thin</groupId> -->
<!-- <artifactId>coder-common-thin-job</artifactId> -->
<!-- <version>${revision}</version> -->
<!-- </dependency> -->
<!-- 代码生成器 -->
<!-- <dependency> -->
<!-- <groupId>org.leocoder.thin</groupId> -->

View File

@ -11,7 +11,6 @@ import org.leocoder.thin.repect.anno.EnableCoderRepeatSubmit;
import org.leocoder.thin.resultex.anno.EnableResultEx;
import org.leocoder.thin.satoken.anno.EnableCoderSaToken;
import org.leocoder.thin.dict.anno.EnableCoderDict;
import org.leocoder.thin.job.anno.EnableCoderJob;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;
@ -32,7 +31,6 @@ import org.springframework.scheduling.annotation.EnableScheduling;
@EnableOperLog
@EnableCoderOss
@EnableCoderDict
@EnableCoderJob
@EnableScheduling
@Slf4j
@SpringBootApplication(scanBasePackages = "org.leocoder.thin")

View File

@ -0,0 +1,26 @@
-- 字典管理按钮权限补充数据
-- 时间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);

View File

@ -1,111 +0,0 @@
-- ============================================================================
-- 定时任务管理功能 SQL 脚本
-- 作者: Leocoder
-- 创建时间: 2025-09-27
-- 描述: 支持动态配置和管理的定时任务系统表结构
-- ============================================================================
-- ----------------------------
-- Table structure for sys_job
-- ----------------------------
DROP TABLE IF EXISTS `sys_job`;
CREATE TABLE `sys_job` (
`job_id` bigint NOT NULL AUTO_INCREMENT COMMENT '任务ID',
`job_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '任务名称',
`job_type` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '任务类型[1-管理平台 2-小程序 3-App]',
`class_path` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '类路径',
`method_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '方法名称',
`cron_expression` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT 'cron表达式',
`policy_status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '1' COMMENT 'cron计划策略[1-立即执行 2-执行一次 3-放弃执行]',
`job_status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '0' COMMENT '任务状态[0-正常 1-暂停]',
`job_params` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '任务参数',
`remark` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '备注',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`create_by` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '创建人',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '修改时间',
`update_by` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '' COMMENT '修改人',
PRIMARY KEY (`job_id`) USING BTREE,
KEY `idx_job_status` (`job_status`),
KEY `idx_job_type` (`job_type`),
KEY `idx_create_time` (`create_time`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC COMMENT='定时任务管理表';
-- ----------------------------
-- Records of sys_job - 初始化示例数据
-- ----------------------------
BEGIN;
INSERT INTO `sys_job` (`job_id`, `job_name`, `job_type`, `class_path`, `method_name`, `cron_expression`, `policy_status`, `job_status`, `job_params`, `remark`, `create_by`) VALUES
(1, '测试任务-单参数', '1', 'org.leocoder.thin.job.task.CoderJobTimerTaskRunner', 'paramAction', '0 0/5 * * * ?', '1', '1', 'CODER-THIN-TEST', '每5分钟执行一次的测试任务', 'Leocoder'),
(2, '测试任务-无参数', '1', 'org.leocoder.thin.job.task.CoderJobTimerTaskRunner', 'noParamAction', '0 0/10 * * * ?', '3', '1', '', '每10分钟执行一次的无参数测试任务', 'Leocoder'),
(3, '测试任务-多参数', '1', 'org.leocoder.thin.job.task.CoderJobTimerTaskRunner', 'manyParamsAction', '0 0 2 * * ?', '1', '1', 'CODER-THIN,999', '每天凌晨2点执行的多参数测试任务', 'Leocoder');
COMMIT;
-- ----------------------------
-- Table structure for sys_job_log (可选,用于任务执行日志记录)
-- ----------------------------
DROP TABLE IF EXISTS `sys_job_log`;
CREATE TABLE `sys_job_log` (
`job_log_id` bigint NOT NULL AUTO_INCREMENT COMMENT '任务日志ID',
`job_id` bigint NOT NULL COMMENT '任务ID',
`job_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '' COMMENT '任务名称',
`job_group` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'DEFAULT' COMMENT '任务组名',
`invoke_target` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL COMMENT '调用目标字符串',
`job_message` varchar(512) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '日志信息',
`status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci DEFAULT '0' COMMENT '执行状态[0-正常 1-失败]',
`exception_info` text CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci COMMENT '异常信息',
`start_time` datetime DEFAULT NULL COMMENT '开始时间',
`stop_time` datetime DEFAULT NULL COMMENT '结束时间',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
PRIMARY KEY (`job_log_id`) USING BTREE,
KEY `idx_job_id` (`job_id`),
KEY `idx_status` (`status`),
KEY `idx_create_time` (`create_time`)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci ROW_FORMAT=DYNAMIC COMMENT='定时任务执行日志表';
-- ----------------------------
-- 添加菜单权限数据 (可选,用于前端菜单展示)
-- ----------------------------
-- 注意以下SQL需要根据实际的菜单权限表结构进行调整
-- 定时任务管理菜单
-- INSERT INTO `sys_menu` (`menu_name`, `parent_id`, `order_num`, `url`, `menu_type`, `visible`, `perms`, `icon`, `create_by`, `remark`) VALUES
-- ('定时任务', 2, 6, '/monitor/job', 'C', '0', 'monitor:job:view', 'job', 'Leocoder', '定时任务菜单');
-- 定时任务按钮权限
-- INSERT INTO `sys_menu` (`menu_name`, `parent_id`, `order_num`, `url`, `menu_type`, `visible`, `perms`, `create_by`, `remark`) VALUES
-- ('任务查询', 最新菜单ID, 1, '#', 'F', '0', 'monitor:job:list', 'Leocoder', ''),
-- ('任务新增', 最新菜单ID, 2, '#', 'F', '0', 'monitor:job:add', 'Leocoder', ''),
-- ('任务修改', 最新菜单ID, 3, '#', 'F', '0', 'monitor:job:edit', 'Leocoder', ''),
-- ('任务删除', 最新菜单ID, 4, '#', 'F', '0', 'monitor:job:delete', 'Leocoder', ''),
-- ('任务执行', 最新菜单ID, 5, '#', 'F', '0', 'monitor:job:run', 'Leocoder', ''),
-- ('状态修改', 最新菜单ID, 6, '#', 'F', '0', 'monitor:job:update', 'Leocoder', '');
-- ============================================================================
-- 说明文档
-- ============================================================================
/*
1.
- sys_job:
- sys_job_log:
2.
- job_id:
- class_path:
- method_name:
- cron_expression: cron表达式
- policy_status: 1/2/3
- job_status: 0/1
- job_params:
- is_delete:
3. 使
- String, Integer, Boolean
-
-
-
4.
- CommonTimerTaskRunner
- cron表达式支持秒级精度6
-
*/