初始化项目

This commit is contained in:
2026-04-21 16:12:04 +08:00
parent 4541af2c63
commit f9d96473da
443 changed files with 36365 additions and 19 deletions

View File

@@ -0,0 +1,76 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://maven.apache.org/POM/4.0.0"
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.xujun</groupId>
<artifactId>xtools-app-sys</artifactId>
<version>1.0.0</version>
</parent>
<artifactId>xtools-app-sys-scheduled</artifactId>
<!-- 依赖 -->
<dependencies>
<!-- xtools begin -->
<dependency>
<groupId>org.xujun</groupId>
<artifactId>xtools-web</artifactId>
</dependency>
<!-- xtools-extend begin -->
<!-- excel 表格工具 -->
<dependency>
<groupId>org.apache.fesod</groupId>
<artifactId>fesod-sheet</artifactId>
</dependency>
<dependency>
<groupId>org.xujun</groupId>
<artifactId>xtools-extend</artifactId>
</dependency>
<!-- xtools-extend end -->
<!-- xtools-boot-core -->
<dependency>
<groupId>org.xujun</groupId>
<artifactId>xtools-boot-core</artifactId>
</dependency>
<!-- xtools-boot-db-mybatis-plus -->
<dependency>
<groupId>org.xujun</groupId>
<artifactId>xtools-boot-db-mybatis-plus</artifactId>
</dependency>
<!-- xtools end -->
<!-- 项目模块 begin -->
<dependency>
<groupId>org.xujun</groupId>
<artifactId>xtools-app-common-log-bus</artifactId>
</dependency>
<!-- 项目模块 end -->
<!-- SpringBoot begin -->
<!-- SpringBoot-Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- SpringBoot end -->
<!-- mapstruct begin -->
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct</artifactId>
</dependency>
<dependency>
<groupId>org.mapstruct</groupId>
<artifactId>mapstruct-processor</artifactId>
</dependency>
<!-- mapstruct end -->
<!-- fastjson2 -->
<dependency>
<groupId>com.alibaba.fastjson2</groupId>
<artifactId>fastjson2</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,171 @@
package xtools.app.sys.scheduled.controller;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import xtools.app.sys.scheduled.model.dto.excel.SysScheduledExcel;
import xtools.app.sys.scheduled.model.dto.req.SysScheduledAddReq;
import xtools.app.sys.scheduled.model.dto.req.SysScheduledPageReq;
import xtools.app.sys.scheduled.model.dto.req.SysScheduledUpdateReq;
import xtools.app.sys.scheduled.model.dto.resp.SysScheduledResp;
import xtools.app.sys.scheduled.service.SysScheduledService;
import xtools.boot.api.exection.BizError;
import xtools.boot.api.exection.BizWarning;
import xtools.boot.api.model.dto.Result;
import xtools.boot.api.model.dto.page.PageReq;
import xtools.boot.api.model.dto.page.PageResp;
import xtools.boot.api.model.dto.req.IdListReq;
import xtools.core.CollectionUtils;
import xtools.extend.office.FesodUtils;
import xtools.web.HttpServletUtils;
import java.io.IOException;
import java.util.List;
/**
* <p>Title : SysScheduledController</p>
* <p>Description : 定时任务 Controller</p>
* <p>Company : org.xujun</p>
*
* @author : xujun
* @version : 1.0.0
* @date : 2026-02-23 10:27:18
*/
@Tag(name = "定时任务")
@RequiredArgsConstructor
@RestController
@RequestMapping("/sys/scheduled")
public class SysScheduledController {
private final SysScheduledService sysScheduledService;
@Operation(summary = "分页请求")
@PostMapping("page")
public Result<PageResp<SysScheduledResp>> page(@RequestBody @Valid PageReq<SysScheduledPageReq> pageReq) {
return sysScheduledService.page(pageReq);
}
@Operation(summary = "获取数据")
@GetMapping("base/{id}")
public Result<SysScheduledResp> getById(
@Schema(description = "ID", example = "1")
@Min(value = 1L, message = "不能小于1")
@NotNull(message = "不能为空")
@PathVariable Long id
) {
return sysScheduledService.getById(id);
}
@Operation(summary = "添加数据")
@PostMapping("base")
public Result<Boolean> add(@RequestBody @Valid SysScheduledAddReq req) {
return sysScheduledService.add(req);
}
@Operation(summary = "更新数据")
@PutMapping("base")
public Result<Boolean> update(@RequestBody @Valid SysScheduledUpdateReq req) {
return sysScheduledService.update(req);
}
@Operation(summary = "删除数据")
@DeleteMapping("base")
public Result<Boolean> delById(@RequestBody @Valid IdListReq req) {
return sysScheduledService.delById(req.getIdList());
}
@Operation(summary = "导出Excel")
@PostMapping("export")
public void exportExcel(@RequestBody @Valid SysScheduledPageReq req, HttpServletResponse response) {
String sheetName = "定时任务";
String filename = sheetName + ".xlsx";
List<SysScheduledExcel> dataList = sysScheduledService.exportExcel(req);
try {
FesodUtils.write(response.getOutputStream(), SysScheduledExcel.class, sheetName, dataList);
} catch (IOException e) {
throw new BizError("导出Excel失败");
}
// 设置 header 和 contentType.写在最后的原因是,避免报错时,响应 contentType 已经被修改
HttpServletUtils.addDownloadHeader(response, filename);
}
@Operation(summary = "导入Excel")
@PostMapping("import")
public Result<Boolean> importExcel(@RequestParam("file") MultipartFile file) {
List<SysScheduledExcel> dataList;
try {
dataList = FesodUtils.read(file.getInputStream(), SysScheduledExcel.class);
} catch (IOException e) {
throw new BizError("导入Excel失败");
}
if (CollectionUtils.isEmpty(dataList)) {
throw new BizWarning("导入Excel没有包含有效数据");
}
sysScheduledService.importExcel(dataList);
return Result.ok(true);
}
@Operation(summary = "运行定时任务")
@PostMapping("run/{id}")
public Result<Boolean> run(
@Schema(description = "ID", example = "1")
@Min(value = 1L, message = "不能小于1")
@NotNull(message = "不能为空")
@PathVariable Long id
) {
sysScheduledService.run(id);
return Result.ok(true);
}
@Operation(summary = "启动定时任务")
@PostMapping("start/{id}")
public Result<Boolean> start(
@Schema(description = "ID", example = "1")
@Min(value = 1L, message = "不能小于1")
@NotNull(message = "不能为空")
@PathVariable Long id
) {
sysScheduledService.start(id);
return Result.ok(true);
}
@Operation(summary = "停止定时任务")
@PostMapping("stop/{id}")
public Result<Boolean> stop(
@Schema(description = "ID", example = "1")
@Min(value = 1L, message = "不能小于1")
@NotNull(message = "不能为空")
@PathVariable Long id
) {
sysScheduledService.stop(id);
return Result.ok(true);
}
@Operation(summary = "重启定时任务")
@PostMapping("restart/{id}")
public Result<Boolean> restart(
@Schema(description = "ID", example = "1")
@Min(value = 1L, message = "不能小于1")
@NotNull(message = "不能为空")
@PathVariable Long id
) {
sysScheduledService.restart(id);
return Result.ok(true);
}
}

View File

@@ -0,0 +1,74 @@
package xtools.app.sys.scheduled.convert;
import org.mapstruct.Mapper;
import org.mapstruct.Mapping;
import xtools.app.sys.scheduled.model.dto.excel.SysScheduledExcel;
import xtools.app.sys.scheduled.model.dto.req.SysScheduledAddReq;
import xtools.app.sys.scheduled.model.dto.req.SysScheduledUpdateReq;
import xtools.app.sys.scheduled.model.dto.resp.SysScheduledResp;
import xtools.app.sys.scheduled.model.entity.SysScheduled;
import java.util.List;
/**
* <p>Title : SysScheduledConvert</p>
* <p>Description : 定时任务 Convert</p>
* <p>Company : org.xujun</p>
*
* @author : xujun
* @version : 1.0.0
* @date : 2026-02-23 10:15:23
*/
@Mapper(componentModel = "spring")
public interface SysScheduledConvert {
/**
* 添加请求转实体
*
* @param req 添加请求
* @return 实体
*/
SysScheduled addReqToEntity(SysScheduledAddReq req);
/**
* 修改请求转实体
*
* @param req 修改请求
* @return 实体
*/
SysScheduled updateReqToEntity(SysScheduledUpdateReq req);
/**
* 实体转响应
*
* @param data 实体
* @return 响应
*/
SysScheduledResp entityToResp(SysScheduled data);
/**
* 批量实体转响应
*
* @param dataList 批量实体
* @return 响应
*/
List<SysScheduledResp> entityToRespList(List<SysScheduled> dataList);
/**
* 实体转Excel
*
* @param data 实体
* @return Excel
*/
SysScheduledExcel entityToExcel(SysScheduled data);
/**
* Excel转实体
*
* @param data Excel
* @return 实体
*/
@Mapping(target = "status", ignore = true)
SysScheduled excelToEntity(SysScheduledExcel data);
}

View File

@@ -0,0 +1,53 @@
package xtools.app.sys.scheduled.init;
import lombok.RequiredArgsConstructor;
import org.jspecify.annotations.NonNull;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import xtools.app.sys.scheduled.service.SysScheduledService;
import xtools.base.config.BaseParams;
import xtools.boot.log.LogBus;
import xtools.boot.log.enums.LogBusBaseType;
import xtools.boot.log.holder.LogTrackHolder;
import xtools.core.enums.LogLevel;
/**
* <p>Title : InitSysScheduled</p>
* <p>Description : InitSysScheduled</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @author : XuJun
* @version : 1.0.0
* @date : 2026/2/2 14:13
*/
@Component
@Order(BaseParams.CP_NUM200)
@RequiredArgsConstructor
public class InitSysScheduled implements ApplicationRunner {
private final SysScheduledService sysScheduledService;
@Override
public void run(@NonNull ApplicationArguments args) {
ScopedValue.where(LogTrackHolder.getScoped(), LogTrackHolder.newMain()).run(() -> {
try {
init();
} catch (Exception e) {
LogBus.init(LogLevel.ERROR, LogBusBaseType.OTHER).title("初始化异常").error(e).save();
}
});
}
/**
* 初始化
*/
private void init() {
sysScheduledService.init();
}
}

View File

@@ -0,0 +1,19 @@
package xtools.app.sys.scheduled.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
import xtools.app.sys.scheduled.model.entity.SysScheduled;
/**
* <p>Title : SysScheduledMapper</p>
* <p>Description : 定时任务 Mapper 接口</p>
* <p>Company : org.xujun</p>
*
* @author : xujun
* @version : 1.0.0
* @date : 2026-02-23 10:15:23
*/
@Mapper
public interface SysScheduledMapper extends BaseMapper<SysScheduled> {
}

View File

@@ -0,0 +1,54 @@
package xtools.app.sys.scheduled.model.dto.excel;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.apache.fesod.sheet.annotation.ExcelProperty;
import java.io.Serializable;
/**
* <p>Title : SysScheduledExcel</p>
* <p>Description : 定时任务Excel对象</p>
* <p>Company : org.xujun</p>
*
* @author : xujun
* @version : 1.0.0
* @date : 2026-02-23 10:15:23
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SysScheduledExcel implements Serializable {
/**
* 描述
*/
@ExcelProperty("任务描述")
private String title;
/**
* 需要执行的SpringBean的名称
*/
@ExcelProperty("Bean名称")
private String beanName;
/**
* 执行参数
*/
@ExcelProperty("执行参数")
private String params;
/**
* 表达式
*/
@ExcelProperty("表达式")
private String cron;
/**
* 状态(默认1)
*/
@ExcelProperty("任务状态")
private String status;
}

View File

@@ -0,0 +1,53 @@
package xtools.app.sys.scheduled.model.dto.req;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* <p>Title : SysScheduledAddReq</p>
* <p>Description : 定时任务添加请求对象</p>
* <p>Company : org.xujun</p>
*
* @author : xujun
* @version : 1.0.0
* @date : 2026-02-23 10:15:23
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SysScheduledAddReq implements Serializable {
/**
* 描述
*/
@Schema(description = "描述")
private String title;
/**
* 需要执行的SpringBean的名称
*/
@Schema(description = "需要执行的SpringBean的名称")
private String beanName;
/**
* 执行参数
*/
@Schema(description = "执行参数")
private String params;
/**
* 表达式
*/
@Schema(description = "表达式")
private String cron;
/**
* 状态(默认1)
*/
@Schema(description = "状态(默认1)")
private Integer status;
}

View File

@@ -0,0 +1,72 @@
package xtools.app.sys.scheduled.model.dto.req;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
import java.time.Instant;
/**
* <p>Title : SysScheduledPageReq</p>
* <p>Description : 定时任务分页请求对象</p>
* <p>Company : org.xujun</p>
*
* @author : xujun
* @version : 1.0.0
* @date : 2026-02-23 10:15:23
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SysScheduledPageReq implements Serializable {
/**
* 主键 ID
*/
@Schema(description = "主键 ID")
private Long id;
/**
* 描述
*/
@Schema(description = "描述")
private String title;
/**
* 需要执行的SpringBean的名称
*/
@Schema(description = "需要执行的SpringBean的名称")
private String beanName;
/**
* 执行参数
*/
@Schema(description = "执行参数")
private String params;
/**
* 表达式
*/
@Schema(description = "表达式")
private String cron;
/**
* 状态(默认1)
*/
@Schema(description = "状态(默认1)")
private Integer status;
/**
* 创建时间(范围)
*/
@Schema(description = "创建时间(范围)", example = "['2026-01-01 00:00:00', '2026-01-01 12:00:00']")
private Instant[] gmtCreateRange;
/**
* 更新时间(范围)
*/
@Schema(description = "更新时间(范围)", example = "['2026-01-01 00:00:00', '2026-01-01 12:00:00']")
private Instant[] gmtModifiedRange;
}

View File

@@ -0,0 +1,60 @@
package xtools.app.sys.scheduled.model.dto.req;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serializable;
/**
* <p>Title : SysScheduledUpdateReq</p>
* <p>Description : 定时任务更新请求对象</p>
* <p>Company : org.xujun</p>
*
* @author : xujun
* @version : 1.0.0
* @date : 2026-02-23 10:15:23
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class SysScheduledUpdateReq implements Serializable {
/**
* 主键 ID
*/
@Schema(description = "主键 ID")
private Long id;
/**
* 描述
*/
@Schema(description = "描述")
private String title;
/**
* 需要执行的SpringBean的名称
*/
@Schema(description = "需要执行的SpringBean的名称")
private String beanName;
/**
* 执行参数
*/
@Schema(description = "执行参数")
private String params;
/**
* 表达式
*/
@Schema(description = "表达式")
private String cron;
/**
* 状态(默认1)
*/
@Schema(description = "状态(默认1)")
private Integer status;
}

View File

@@ -0,0 +1,66 @@
package xtools.app.sys.scheduled.model.dto.resp;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import xtools.boot.api.model.entity.BaseEntity;
/**
* <p>Title : SysScheduledResp</p>
* <p>Description : 定时任务响应对象</p>
* <p>Company : org.xujun</p>
*
* @author : xujun
* @version : 1.0.0
* @date : 2026-02-23 10:15:23
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
public class SysScheduledResp extends BaseEntity {
/**
* 主键 ID
*/
@Schema(description = "主键 ID")
private Long id;
/**
* 描述
*/
@Schema(description = "描述")
private String title;
/**
* 需要执行的SpringBean的名称
*/
@Schema(description = "需要执行的SpringBean的名称")
private String beanName;
/**
* 执行参数
*/
@Schema(description = "执行参数")
private String params;
/**
* 表达式
*/
@Schema(description = "表达式")
private String cron;
/**
* 状态(默认1)
*/
@Schema(description = "状态")
private Integer status;
/**
* 运行状态
*/
@Schema(description = "运行状态")
private Integer run;
}

View File

@@ -0,0 +1,71 @@
package xtools.app.sys.scheduled.model.entity;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import xtools.boot.api.model.entity.BaseEntity;
/**
* <p>Title : SysScheduled</p>
* <p>Description : 定时任务实体对象</p>
* <p>Company : org.xujun</p>
*
* @author : xujun
* @version : 1.0.0
* @date : 2026-02-23 10:15:23
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@EqualsAndHashCode(callSuper = true)
@TableName("sys_scheduled")
public class SysScheduled extends BaseEntity {
/**
* 主键 ID
*/
@Schema(description = "主键 ID")
@TableId(value = "id", type = IdType.ASSIGN_ID)
private Long id;
/**
* 描述
*/
@Schema(description = "描述")
@TableField(value = "title")
private String title;
/**
* 需要执行的SpringBean的名称
*/
@Schema(description = "需要执行的SpringBean的名称")
@TableField(value = "bean_name")
private String beanName;
/**
* 执行参数
*/
@Schema(description = "执行参数")
@TableField(value = "params")
private String params;
/**
* 表达式
*/
@Schema(description = "表达式")
@TableField(value = "cron")
private String cron;
/**
* 状态(默认1)
*/
@Schema(description = "状态(默认1)")
@TableField(value = "status")
private Integer status;
}

View File

@@ -0,0 +1,113 @@
package xtools.app.sys.scheduled.service;
import xtools.app.sys.scheduled.model.dto.excel.SysScheduledExcel;
import xtools.app.sys.scheduled.model.dto.req.SysScheduledAddReq;
import xtools.app.sys.scheduled.model.dto.req.SysScheduledPageReq;
import xtools.app.sys.scheduled.model.dto.req.SysScheduledUpdateReq;
import xtools.app.sys.scheduled.model.dto.resp.SysScheduledResp;
import xtools.boot.api.model.dto.Result;
import xtools.boot.api.model.dto.page.PageReq;
import xtools.boot.api.model.dto.page.PageResp;
import java.util.List;
/**
* <p>Title : SysScheduledService</p>
* <p>Description : 定时任务 Service</p>
* <p>Company : org.xujun</p>
*
* @author : xujun
* @version : 1.0.0
* @date : 2026-02-23 10:15:23
*/
public interface SysScheduledService {
/**
* 分页查询
*
* @param pageReq 分页请求
* @return 分页结果
*/
Result<PageResp<SysScheduledResp>> page(PageReq<SysScheduledPageReq> pageReq);
/**
* 根据 ID 查询
*
* @param id ID
* @return 结果
*/
Result<SysScheduledResp> getById(Long id);
/**
* 添加
*
* @param req 添加请求
* @return 添加结果
*/
Result<Boolean> add(SysScheduledAddReq req);
/**
* 修改
*
* @param req 修改请求
* @return 修改结果
*/
Result<Boolean> update(SysScheduledUpdateReq req);
/**
* 根据 ID 删除
*
* @param idList ID 集合
* @return 删除结果
*/
Result<Boolean> delById(List<Long> idList);
/**
* 导出 Excel
*
* @param req 请求参数
* @return Excel 数据
*/
List<SysScheduledExcel> exportExcel(SysScheduledPageReq req);
/**
* 导入 Excel
*
* @param dataList Excel 数据
*/
void importExcel(List<SysScheduledExcel> dataList);
/**
* 初始化定时任务
*/
void init();
/**
* 运行定时任务
*
* @param id 定时任务ID
*/
void run(Long id);
/**
* 启动定时任务
*
* @param id 定时任务ID
*/
void start(Long id);
/**
* 停止定时任务
*
* @param id 定时任务ID
*/
void stop(Long id);
/**
* 重启定时任务
*
* @param id 定时任务ID
*/
void restart(Long id);
}

View File

@@ -0,0 +1,19 @@
package xtools.app.sys.scheduled.service.base;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import org.springframework.stereotype.Component;
import xtools.app.sys.scheduled.mapper.SysScheduledMapper;
import xtools.app.sys.scheduled.model.entity.SysScheduled;
/**
* <p>Title : SysScheduledBaseService</p>
* <p>Description : 定时任务 BaseService</p>
* <p>Company : org.xujun</p>
*
* @author : xujun
* @version : 1.0.0
* @date : 2026-02-23 10:15:23
*/
@Component
public class SysScheduledBaseService extends ServiceImpl<SysScheduledMapper, SysScheduled> {
}

View File

@@ -0,0 +1,389 @@
package xtools.app.sys.scheduled.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import xtools.app.sys.scheduled.convert.SysScheduledConvert;
import xtools.app.sys.scheduled.model.dto.excel.SysScheduledExcel;
import xtools.app.sys.scheduled.model.dto.req.SysScheduledAddReq;
import xtools.app.sys.scheduled.model.dto.req.SysScheduledPageReq;
import xtools.app.sys.scheduled.model.dto.req.SysScheduledUpdateReq;
import xtools.app.sys.scheduled.model.dto.resp.SysScheduledResp;
import xtools.app.sys.scheduled.model.entity.SysScheduled;
import xtools.app.sys.scheduled.service.SysScheduledService;
import xtools.app.sys.scheduled.service.base.SysScheduledBaseService;
import xtools.app.sys.scheduled.utils.ScheduledUtils;
import xtools.base.config.BaseParams;
import xtools.boot.api.enums.StatusEnum;
import xtools.boot.api.exection.BizError;
import xtools.boot.api.exection.BizWarning;
import xtools.boot.api.model.dto.Result;
import xtools.boot.api.model.dto.page.PageReq;
import xtools.boot.api.model.dto.page.PageResp;
import xtools.boot.core.utils.SpringContextUtils;
import xtools.boot.db.mybatisplus.utils.QueryUtils;
import xtools.boot.log.LogBus;
import xtools.boot.log.enums.LogBusBaseType;
import xtools.core.CollectionUtils;
import xtools.core.StringUtils;
import xtools.core.enums.LogLevel;
import java.util.List;
import java.util.Objects;
/**
* <p>Title : SysScheduledServiceImpl</p>
* <p>Description : 定时任务 ServiceImpl</p>
* <p>Company : org.xujun</p>
*
* @author : xujun
* @version : 1.0.0
* @date : 2026-02-23 10:15:23
*/
@Slf4j
@Primary
@Service
@RequiredArgsConstructor
public class SysScheduledServiceImpl implements SysScheduledService, BaseParams {
private final SysScheduledBaseService sysScheduledBaseService;
private final SysScheduledConvert sysScheduledConvert;
/**
* 分页查询
*
* @param pageReq 分页请求
* @return 分页结果
*/
@Override
public Result<PageResp<SysScheduledResp>> page(PageReq<SysScheduledPageReq> pageReq) {
// 分页查询
Page<SysScheduled> page = getPageData(pageReq.getCurrentPage(), pageReq.getPageSize(), pageReq.getQuery());
List<SysScheduledResp> dataList = sysScheduledConvert.entityToRespList(page.getRecords());
if (CollectionUtils.isNotEmpty(dataList)) {
dataList.forEach(item -> item.setRun(ScheduledUtils.isRun(item.getId()) ? CP_NUM1 : CP_NUM0));
}
// 分装结果
PageResp<SysScheduledResp> pageResp = new PageResp<>(pageReq, page.getTotal(), dataList);
return Result.ok(pageResp);
}
/**
* 根据 ID 查询
*
* @param id ID
* @return 结果
*/
@Override
public Result<SysScheduledResp> getById(Long id) {
SysScheduled data = sysScheduledBaseService.getById(id);
return Result.ok(sysScheduledConvert.entityToResp(data));
}
/**
* 添加
*
* @param req 添加请求
* @return 添加结果
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Result<Boolean> add(SysScheduledAddReq req) {
SysScheduled entity = sysScheduledConvert.addReqToEntity(req);
if (Objects.nonNull(existsEntity(entity))) {
throw new BizWarning("数据已存在");
}
return Result.ok(sysScheduledBaseService.save(entity));
}
/**
* 修改
*
* @param req 修改请求
* @return 修改结果
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Result<Boolean> update(SysScheduledUpdateReq req) {
SysScheduled entity = sysScheduledConvert.updateReqToEntity(req);
if (ScheduledUtils.isRun(entity.getId())) {
throw new BizWarning("任务正在运行中,请先停止任务");
}
if (Objects.nonNull(existsEntity(entity))) {
throw new BizWarning("数据已存在");
}
return Result.ok(sysScheduledBaseService.updateById(entity));
}
/**
* 根据 ID 删除
*
* @param idList ID 集合
* @return 删除结果
*/
@Override
@Transactional(rollbackFor = Exception.class)
public Result<Boolean> delById(List<Long> idList) {
idList.forEach(id -> {
if (ScheduledUtils.isRun(id)) {
throw new BizWarning("有任务正在运行中,请先停止任务");
}
});
boolean removed = sysScheduledBaseService.removeByIds(idList);
if (!removed) {
throw new BizError("删除失败");
}
return Result.ok(true);
}
/**
* 导出 Excel
*
* @param req 请求参数
* @return Excel 数据
*/
@Override
public List<SysScheduledExcel> exportExcel(SysScheduledPageReq req) {
// 创建查询条件
LambdaQueryWrapper<SysScheduled> query = new LambdaQueryWrapper<>();
// 查询字段
query.select(
SysScheduled::getTitle
, SysScheduled::getBeanName
, SysScheduled::getParams
, SysScheduled::getCron
, SysScheduled::getStatus
);
// 设置查询条件
setQueryWrapper(query, req);
// 排序
query.orderByDesc(SysScheduled::getGmtCreate);
List<SysScheduled> dataList = sysScheduledBaseService.list(query);
return dataList.stream().map(item -> {
StatusEnum status = StatusEnum.valueOf(item.getStatus());
SysScheduledExcel excel = sysScheduledConvert.entityToExcel(item);
excel.setStatus(status.desc());
return excel;
}).toList();
}
/**
* 导入 Excel
*
* @param dataList Excel 数据
*/
@Override
@Transactional(rollbackFor = Exception.class)
public void importExcel(List<SysScheduledExcel> dataList) {
for (SysScheduledExcel excel : dataList) {
StatusEnum status = StatusEnum.valueOfDesc(excel.getStatus());
SysScheduled item = sysScheduledConvert.excelToEntity(excel);
item.setStatus(status.code());
SysScheduled dbItem = existsEntity(item);
if (Objects.isNull(dbItem)) {
// 新增
sysScheduledBaseService.save(item);
} else {
// 修改
item.setId(dbItem.getId());
sysScheduledBaseService.updateById(item);
}
}
}
/**
* 获取分页数据
*
* @param currentPage 当前页
* @param pageSize 每页数量
* @param req 请求参数
* @return 分页数据
*/
private Page<SysScheduled> getPageData(Integer currentPage, Integer pageSize, SysScheduledPageReq req) {
// 创建查询条件
LambdaQueryWrapper<SysScheduled> query = new LambdaQueryWrapper<>();
// 查询字段
query.select(
SysScheduled::getId
, SysScheduled::getTitle
, SysScheduled::getBeanName
, SysScheduled::getParams
, SysScheduled::getCron
, SysScheduled::getStatus
, SysScheduled::getGmtCreate
, SysScheduled::getGmtModified
);
// 设置查询条件
setQueryWrapper(query, req);
// 排序
query.orderByDesc(SysScheduled::getGmtCreate);
return sysScheduledBaseService.page(QueryUtils.getPage(currentPage, pageSize), query);
}
/**
* 设置查询条件
*
* @param query 查询条件
* @param req 请求参数
*/
private void setQueryWrapper(LambdaQueryWrapper<SysScheduled> query, SysScheduledPageReq req) {
if (Objects.isNull(req)) {
return;
}
// 查询条件
query.eq(Objects.nonNull(req.getId()), SysScheduled::getId, req.getId());
query.like(StringUtils.isNotBlank(req.getTitle()), SysScheduled::getTitle, req.getTitle());
query.like(StringUtils.isNotBlank(req.getBeanName()), SysScheduled::getBeanName, req.getBeanName());
query.like(StringUtils.isNotBlank(req.getParams()), SysScheduled::getParams, req.getParams());
query.like(StringUtils.isNotBlank(req.getCron()), SysScheduled::getCron, req.getCron());
query.eq(Objects.nonNull(req.getStatus()), SysScheduled::getStatus, req.getStatus());
QueryUtils.addTimeRange(query, req.getGmtCreateRange(), SysScheduled::getGmtCreate);
QueryUtils.addTimeRange(query, req.getGmtModifiedRange(), SysScheduled::getGmtModified);
}
/**
* 判断实体是否存在
*
* @param entity 实体
* @return 是否存在
*/
private SysScheduled existsEntity(SysScheduled entity) {
// 创建查询条件
LambdaQueryWrapper<SysScheduled> query = new LambdaQueryWrapper<>();
// 查询字段
query.select(SysScheduled::getId);
// 排除当前数据
query.ne(Objects.nonNull(entity.getId()), SysScheduled::getId, entity.getId());
// 校验数据时候存在的条件
query.eq(StringUtils.isNotBlank(entity.getBeanName()), SysScheduled::getBeanName, entity.getBeanName());
return sysScheduledBaseService.getOne(query);
}
/**
* 初始化定时任务
*/
@Override
public void init() {
// 创建查询条件
LambdaQueryWrapper<SysScheduled> query = new LambdaQueryWrapper<>();
// 查询字段
query.select(
SysScheduled::getId
, SysScheduled::getTitle
, SysScheduled::getBeanName
, SysScheduled::getParams
, SysScheduled::getCron
);
query.eq(SysScheduled::getStatus, StatusEnum.NORMAL.code());
List<SysScheduled> dataList = sysScheduledBaseService.list(query);
if (CollectionUtils.isEmpty(dataList)) {
log.info("初始化定时任务成功,无定时任务");
return;
}
for (SysScheduled item : dataList) {
String beanName = item.getBeanName();
Runnable task = getTask(beanName);
if (Objects.isNull(task)) {
LogBus.init(LogLevel.ERROR, LogBusBaseType.JOB).title("定时任务不存在").data(item).save();
continue;
}
try {
ScheduledUtils.add(item.getId(), task, item.getCron());
log.info("定时任务添加成功,任务名称:{}", item.getTitle());
} catch (Exception e) {
LogBus.init(LogLevel.ERROR, LogBusBaseType.JOB).title("定时任务添加异常").error(e).data(item).save();
}
}
}
/**
* 运行定时任务
*
* @param id 定时任务ID
*/
@Override
public void run(Long id) {
SysScheduled item = sysScheduledBaseService.getById(id);
if (Objects.isNull(item)) {
throw new BizError("定时任务不存在");
}
boolean run = ScheduledUtils.isRun(id);
if (run) {
throw new BizError("定时任务已启动");
}
Runnable task = getTask(item.getBeanName());
if (Objects.isNull(task)) {
throw new BizError("定时任务bean不存在");
}
task.run();
}
/**
* 启动定时任务
*
* @param id 定时任务ID
*/
@Override
public void start(Long id) {
SysScheduled item = sysScheduledBaseService.getById(id);
if (Objects.isNull(item)) {
throw new BizError("定时任务不存在");
}
boolean run = ScheduledUtils.isRun(id);
if (run) {
throw new BizError("定时任务已启动");
}
ScheduledUtils.add(id, getTask(item.getBeanName()), item.getCron());
}
/**
* 停止定时任务
*
* @param id 定时任务ID
*/
@Override
public void stop(Long id) {
SysScheduled item = sysScheduledBaseService.getById(id);
if (Objects.isNull(item)) {
throw new BizError("定时任务不存在");
}
boolean run = ScheduledUtils.isRun(id);
if (!run) {
throw new BizError("定时任务未启动");
}
ScheduledUtils.remove(id);
}
/**
* 重启定时任务
*
* @param id 定时任务ID
*/
@Override
public void restart(Long id) {
stop(id);
start(id);
}
/**
* 获取任务
*
* @param beanName 任务bean名称
* @return 任务
*/
private Runnable getTask(String beanName) {
Object obj = SpringContextUtils.getBeanDefNull(beanName);
if (obj instanceof Runnable task) {
return task;
}
return null;
}
}

View File

@@ -0,0 +1,112 @@
package xtools.app.sys.scheduled.utils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.TaskScheduler;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.scheduling.support.CronTrigger;
import xtools.base.config.BaseParams;
import xtools.boot.api.exection.BizError;
import xtools.core.StringUtils;
import xtools.core.extend.CheckUtils;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ScheduledFuture;
/**
* <p>Title : ScheduledUtils</p>
* <p>Description : ScheduledUtils</p>
* <p>DevelopTools : Idea_x64_v2026.1</p>
* <p>DevelopSystem : macOS Sequoia 15.7.5</p>
* <p>Company : org.xujun</p>
*
* @author : XuJun
* @version : 1.0.0
* @date : 2026/2/23 09:46
*/
@Slf4j
public class ScheduledUtils implements BaseParams {
/**
* 任务线程池
*/
private final static TaskScheduler TASK_SCHEDULER;
/**
* 任务列表
*/
private static final ConcurrentHashMap<Long, ScheduledFuture<?>> TASKS = new ConcurrentHashMap<>();
// 初始化
static {
// 创建任务虚拟线程池
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
scheduler.setVirtualThreads(true);
scheduler.initialize();
TASK_SCHEDULER = scheduler;
}
/**
* 添加任务
*
* @param id 任务ID
* @param task 任务执行线程
* @param cron 执行时间表达式
*/
public static void add(Long id, Runnable task, String cron) {
// 参数校验
if (!CheckUtils.id(id) || Objects.isNull(task) || StringUtils.isBlank(cron)) {
throw new BizError("参数错误");
}
// 如果任务已存在,先取消
if (TASKS.containsKey(id)) {
remove(id);
}
CronTrigger trigger;
try {
trigger = new CronTrigger(cron);
} catch (Exception e) {
throw new BizError("时间表达式错误");
}
// 用虚拟线程包装任务
Runnable virtualTask = () -> Thread.startVirtualThread(task);
ScheduledFuture<?> future = TASK_SCHEDULER.schedule(virtualTask, trigger);
if (Objects.isNull(future)) {
throw new BizError("定时任务添加失败");
}
TASKS.put(id, future);
}
/**
* 删除任务
*
* @param id 任务ID
*/
public static void remove(Long id) {
ScheduledFuture<?> future = TASKS.remove(id);
if (Objects.isNull(future)) {
return;
}
future.cancel(true);
}
/**
* 是否在运行
*
* @param id 任务ID
* @return true or false
*/
public static boolean isRun(Long id) {
if (!CheckUtils.id(id)) {
return false;
}
// 获取任务实例
ScheduledFuture<?> task = TASKS.get(id);
if (Objects.isNull(task)) {
return false;
}
return !task.isCancelled();
}
}