feat: 定时任务模块 - 青龙面板集成与任务分组

- 增强 TaskInfoEntity: 新增 groupId, qlScriptId, qlScriptName, lastDuration, lastRunTime, failCount 字段
- 增强 TaskLogEntity: 新增 duration 字段
- 新增 TaskGroupEntity/mapper/service/controller: 任务分组管理
- 新增 QlUserConfigEntity/mapper/service: 每用户青龙App凭证存储
- 新增 QinglongService: 青龙Open API代理,支持每用户独立token
- 新增 AdminQinglongController: 青龙脚本/定时/环境变量API代理
- 新增 AdminTaskGroupController: 任务分组CRUD
- 修改 TaskInfoServiceImpl: 支持青龙脚本类型(taskType=2)跳过Quartz
- 修改 AdminTaskInfoController: 增加groupId过滤和关键词搜索
- 修改 BaseSysLoginServiceImpl: 登录时自动为用户创建青龙App
- 更新 application.yml/application-prod.yml: 青龙面板配置
- 更新菜单和数据库种子数据
This commit is contained in:
OpenCode
2026-07-10 20:40:25 +08:00
parent 34e224c922
commit 754b7f4782
20 changed files with 858 additions and 29 deletions
@@ -46,6 +46,8 @@ public class BaseSysLoginServiceImpl implements BaseSysLoginService {
private final BaseSysPermsService baseSysPermsService;
private final com.cool.modules.task.service.QinglongService qinglongService;
@org.springframework.beans.factory.annotation.Value("${cool.captcha.enable:true}")
private boolean captchaEnable;
@@ -104,6 +106,7 @@ public class BaseSysLoginServiceImpl implements BaseSysLoginService {
if (captchaEnable) {
coolCache.del("verify:img:" + baseSysLoginDto.getCaptchaId());
}
qinglongService.ensureUserApp(baseSysUserEntity.getId());
return generateToken(roleIds, baseSysUserEntity, null);
}
@@ -0,0 +1,168 @@
package com.cool.modules.task.controller.admin;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import com.cool.core.request.R;
import com.cool.core.util.CoolSecurityUtil;
import com.cool.modules.task.service.QinglongService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
@Tag(name = "青龙面板", description = "青龙面板API代理")
@RestController
@RequestMapping("/admin/qinglong")
@RequiredArgsConstructor
public class AdminQinglongController {
final private QinglongService qinglongService;
private Long currentUserId() {
return CoolSecurityUtil.getCurrentUserId();
}
@Operation(summary = "检查连接")
@GetMapping("/check")
public R check() {
return R.ok(qinglongService.checkConnection(currentUserId()));
}
@Operation(summary = "获取脚本列表")
@GetMapping("/scripts")
public R scripts(@RequestParam(required = false) String searchValue,
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "20") int size) {
return R.ok(qinglongService.getScripts(currentUserId(), searchValue, page, size));
}
@Operation(summary = "获取脚本详情")
@GetMapping("/scripts/{fileName}")
public R scriptDetail(@PathVariable String fileName) {
return R.ok(qinglongService.getScript(currentUserId(), fileName));
}
@Operation(summary = "添加脚本")
@PostMapping("/scripts")
public R addScript(@RequestBody JSONObject body) {
return R.ok(qinglongService.addScript(currentUserId(), body));
}
@Operation(summary = "更新脚本")
@PutMapping("/scripts/{fileName}")
public R updateScript(@PathVariable String fileName, @RequestBody JSONObject body) {
return R.ok(qinglongService.updateScript(currentUserId(), fileName, body));
}
@Operation(summary = "删除脚本")
@DeleteMapping("/scripts")
public R deleteScript(@RequestBody JSONArray fileNames) {
return R.ok(qinglongService.deleteScript(currentUserId(), fileNames.toList(String.class)));
}
@Operation(summary = "运行脚本")
@PostMapping("/scripts/run")
public R runScript(@RequestBody JSONObject body) {
return R.ok(qinglongService.runScript(currentUserId(), body));
}
@Operation(summary = "停止脚本")
@PostMapping("/scripts/stop")
public R stopScript(@RequestBody JSONObject body) {
return R.ok(qinglongService.stopScript(currentUserId(), body));
}
@Operation(summary = "获取环境变量列表")
@GetMapping("/envs")
public R envs(@RequestParam(required = false) String searchValue,
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "20") int size) {
return R.ok(qinglongService.getEnvs(currentUserId(), searchValue, page, size));
}
@Operation(summary = "添加环境变量")
@PostMapping("/envs")
public R addEnv(@RequestBody JSONArray envs) {
return R.ok(qinglongService.addEnv(currentUserId(), envs));
}
@Operation(summary = "更新环境变量")
@PutMapping("/envs")
public R updateEnv(@RequestBody JSONObject body) {
return R.ok(qinglongService.updateEnv(currentUserId(), body));
}
@Operation(summary = "删除环境变量")
@DeleteMapping("/envs")
public R deleteEnv(@RequestBody JSONArray ids) {
return R.ok(qinglongService.deleteEnv(currentUserId(), ids));
}
@Operation(summary = "启用环境变量")
@PutMapping("/envs/enable")
public R enableEnv(@RequestBody JSONArray ids) {
return R.ok(qinglongService.enableEnv(currentUserId(), ids));
}
@Operation(summary = "禁用环境变量")
@PutMapping("/envs/disable")
public R disableEnv(@RequestBody JSONArray ids) {
return R.ok(qinglongService.disableEnv(currentUserId(), ids));
}
@Operation(summary = "获取青龙定时任务列表")
@GetMapping("/crons")
public R crons(@RequestParam(required = false) String searchValue,
@RequestParam(defaultValue = "1") int page,
@RequestParam(defaultValue = "20") int size) {
return R.ok(qinglongService.getCrons(currentUserId(), searchValue, page, size));
}
@Operation(summary = "添加青龙定时任务")
@PostMapping("/crons")
public R addCron(@RequestBody JSONObject body) {
return R.ok(qinglongService.addCron(currentUserId(), body));
}
@Operation(summary = "更新青龙定时任务")
@PutMapping("/crons")
public R updateCron(@RequestBody JSONObject body) {
return R.ok(qinglongService.updateCron(currentUserId(), body));
}
@Operation(summary = "删除青龙定时任务")
@DeleteMapping("/crons")
public R deleteCron(@RequestBody JSONArray ids) {
return R.ok(qinglongService.deleteCron(currentUserId(), ids));
}
@Operation(summary = "运行青龙定时任务")
@PutMapping("/crons/run")
public R runCron(@RequestBody JSONArray ids) {
return R.ok(qinglongService.runCron(currentUserId(), ids));
}
@Operation(summary = "停止青龙定时任务")
@PutMapping("/crons/stop")
public R stopCron(@RequestBody JSONArray ids) {
return R.ok(qinglongService.stopCron(currentUserId(), ids));
}
@Operation(summary = "启用青龙定时任务")
@PutMapping("/crons/enable")
public R enableCron(@RequestBody JSONArray ids) {
return R.ok(qinglongService.enableCron(currentUserId(), ids));
}
@Operation(summary = "禁用青龙定时任务")
@PutMapping("/crons/disable")
public R disableCron(@RequestBody JSONArray ids) {
return R.ok(qinglongService.disableCron(currentUserId(), ids));
}
@Operation(summary = "获取青龙任务日志")
@GetMapping("/crons/{cronId}/log")
public R cronLog(@PathVariable String cronId) {
return R.ok(qinglongService.getCronLog(currentUserId(), cronId));
}
}
@@ -0,0 +1,22 @@
package com.cool.modules.task.controller.admin;
import static com.cool.modules.task.entity.table.TaskGroupEntityTableDef.TASK_GROUP_ENTITY;
import cn.hutool.json.JSONObject;
import com.cool.core.annotation.CoolRestController;
import com.cool.core.base.BaseController;
import com.cool.modules.task.entity.TaskGroupEntity;
import com.cool.modules.task.service.TaskGroupService;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
@Tag(name = "任务分组", description = "任务分组管理")
@CoolRestController(api = { "add", "delete", "update", "page", "list", "info" })
public class AdminTaskGroupController extends BaseController<TaskGroupService, TaskGroupEntity> {
@Override
protected void init(HttpServletRequest request, JSONObject requestParams) {
setPageOption(createOp().keyWordLikeFields(TASK_GROUP_ENTITY.NAME).fieldEq(TASK_GROUP_ENTITY.ID));
setListOption(createOp().select(TASK_GROUP_ENTITY.ID, TASK_GROUP_ENTITY.NAME, TASK_GROUP_ENTITY.ORDER_NUM));
}
}
@@ -16,16 +16,15 @@ import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestAttribute;
/**
* 任务
*/
@Tag(name = "任务管理", description = "统一管理任务")
@CoolRestController(api = { "add", "delete", "update", "info", "page" })
public class AdminTaskInfoController extends BaseController<TaskInfoService, TaskInfoEntity> {
@Override
protected void init(HttpServletRequest request, JSONObject requestParams) {
setPageOption(createOp().fieldEq(TASK_INFO_ENTITY.STATUS, TASK_INFO_ENTITY.TYPE));
setPageOption(createOp()
.fieldEq(TASK_INFO_ENTITY.STATUS, TASK_INFO_ENTITY.TYPE, TASK_INFO_ENTITY.GROUP_ID)
.keyWordLikeFields(TASK_INFO_ENTITY.NAME, TASK_INFO_ENTITY.SERVICE));
}
@Operation(summary = "执行一次")
@@ -52,9 +51,9 @@ public class AdminTaskInfoController extends BaseController<TaskInfoService, Tas
@Operation(summary = "任务日志")
@GetMapping("/log")
public R log(@RequestAttribute JSONObject requestParams) {
Integer page = requestParams.getInt("page", 0);
Integer page = requestParams.getInt("page", 1);
Integer size = requestParams.getInt("size", 20);
return R.ok(pageResult((Page) service.log(new Page<>(page, size), requestParams.getLong("id"),
requestParams.getInt("status"))));
return R.ok(pageResult((Page) service.log(new Page<>(page, size),
requestParams.getLong("id"), requestParams.getInt("status"))));
}
}
@@ -0,0 +1,36 @@
package com.cool.modules.task.entity;
import com.cool.core.base.BaseEntity;
import com.mybatisflex.annotation.Table;
import com.cool.core.annotation.ColumnDefine;
import lombok.Getter;
import lombok.Setter;
import org.dromara.autotable.annotation.Index;
@Getter
@Setter
@Table(value = "ql_user_config", comment = "青龙用户配置")
public class QlUserConfigEntity extends BaseEntity<QlUserConfigEntity> {
@Index
@ColumnDefine(comment = "系统用户ID", notNull = true, type = "bigint")
private Long userId;
@ColumnDefine(comment = "系统用户名", notNull = true)
private String username;
@ColumnDefine(comment = "青龙应用ID")
private Long qlAppId;
@ColumnDefine(comment = "青龙client_id")
private String clientId;
@ColumnDefine(comment = "青龙client_secret")
private String clientSecret;
@ColumnDefine(comment = "青龙access_token")
private String accessToken;
@ColumnDefine(comment = "token过期时间戳")
private Long tokenExpire;
}
@@ -0,0 +1,22 @@
package com.cool.modules.task.entity;
import com.cool.core.base.BaseEntity;
import com.mybatisflex.annotation.Table;
import com.cool.core.annotation.ColumnDefine;
import lombok.Getter;
import lombok.Setter;
@Getter
@Setter
@Table(value = "task_group", comment = "任务分组")
public class TaskGroupEntity extends BaseEntity<TaskGroupEntity> {
@ColumnDefine(comment = "分组名称", notNull = true)
private String name;
@ColumnDefine(comment = "排序号", defaultValue = "0")
private Integer orderNum;
@ColumnDefine(comment = "备注")
private String remark;
}
@@ -7,14 +7,13 @@ import com.cool.core.annotation.ColumnDefine;
import java.util.Date;
import lombok.Getter;
import lombok.Setter;
import org.dromara.autotable.annotation.Index;
@Getter
@Setter
@Table(value = "task_info", comment = "任务信息")
public class TaskInfoEntity extends BaseEntity<TaskInfoEntity> {
/**
* 任务调度参数key
*/
@Column(ignore = true)
public static final String JOB_PARAM_KEY = "JOB_PARAM_KEY";
@@ -30,16 +29,16 @@ public class TaskInfoEntity extends BaseEntity<TaskInfoEntity> {
@ColumnDefine(comment = "每间隔多少毫秒执行一次 如果cron设置了 这项设置就无效")
private Integer every;
@ColumnDefine(comment = "状态 0:停止 1:运行", defaultValue = "1", notNull = true)
@ColumnDefine(comment = "状态 0:停止 1:运行 2:异常", defaultValue = "1", notNull = true)
private Integer status;
@ColumnDefine(comment = "服务实例名称")
private String service;
@ColumnDefine(comment = "状态 0:cron 1:时间间隔", defaultValue = "0")
@ColumnDefine(comment = "任务类型 0:cron 1:时间间隔 2:青龙脚本", defaultValue = "0")
private Integer taskType;
@ColumnDefine(comment = "状态 0:系统 1:用户", defaultValue = "0")
@ColumnDefine(comment = "来源类型 0:系统 1:用户", defaultValue = "0")
private Integer type;
@ColumnDefine(comment = "任务数据")
@@ -48,7 +47,7 @@ public class TaskInfoEntity extends BaseEntity<TaskInfoEntity> {
@ColumnDefine(comment = "备注")
private String remark;
@ColumnDefine(comment = "cron")
@ColumnDefine(comment = "cron表达式")
private String cron;
@ColumnDefine(comment = "下一次执行时间")
@@ -59,4 +58,26 @@ public class TaskInfoEntity extends BaseEntity<TaskInfoEntity> {
@ColumnDefine(comment = "结束时间")
private Date endDate;
@Index
@ColumnDefine(comment = "分组ID", type = "bigint")
private Long groupId;
@ColumnDefine(comment = "青龙脚本ID")
private String qlScriptId;
@ColumnDefine(comment = "青龙脚本名称")
private String qlScriptName;
@ColumnDefine(comment = "上次执行耗时(毫秒)")
private Long lastDuration;
@ColumnDefine(comment = "上次执行时间")
private Date lastRunTime;
@ColumnDefine(comment = "连续失败次数", defaultValue = "0")
private Integer failCount;
@Column(ignore = true)
private String groupName;
}
@@ -17,13 +17,15 @@ public class TaskLogEntity extends BaseEntity<TaskLogEntity> {
@ColumnDefine(comment = "任务ID", notNull = true, type = "bigint")
private Long taskId;
@ColumnDefine(comment = "状态 0:失败 1:成功", defaultValue = "0")
@ColumnDefine(comment = "状态 0:失败 1:成功", defaultValue = "0")
private Integer status;
@ColumnDefine(comment = "详情", type = "text")
private String detail;
// 任务名称
@ColumnDefine(comment = "执行耗时(毫秒)")
private Long duration;
@Column(ignore = true)
private String taskName;
}
@@ -0,0 +1,7 @@
package com.cool.modules.task.mapper;
import com.mybatisflex.core.BaseMapper;
import com.cool.modules.task.entity.QlUserConfigEntity;
public interface QlUserConfigMapper extends BaseMapper<QlUserConfigEntity> {
}
@@ -0,0 +1,7 @@
package com.cool.modules.task.mapper;
import com.mybatisflex.core.BaseMapper;
import com.cool.modules.task.entity.TaskGroupEntity;
public interface TaskGroupMapper extends BaseMapper<TaskGroupEntity> {
}
@@ -0,0 +1,327 @@
package com.cool.modules.task.service;
import cn.hutool.core.util.ObjUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.http.HttpRequest;
import cn.hutool.http.HttpResponse;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.cool.modules.task.entity.QlUserConfigEntity;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Service;
@Slf4j
@Service
@ConfigurationProperties(prefix = "qinglong")
@Data
@RequiredArgsConstructor
public class QinglongService {
private String url;
private String clientId;
private String clientSecret;
private String adminUsername;
private String adminPassword;
private String adminToken;
private boolean enabled;
private final QlUserConfigService qlUserConfigService;
private String getAdminInternalToken() {
if (!enabled) return null;
try {
String loginUrl = url + "/api/user/login";
JSONObject body = new JSONObject();
body.set("username", adminUsername);
body.set("password", adminPassword);
HttpResponse resp = HttpRequest.post(loginUrl)
.header("Content-Type", "application/json")
.body(body.toString())
.timeout(10000).execute();
JSONObject json = JSONUtil.parseObj(resp.body());
if (json.getInt("code") == 200) {
return json.getJSONObject("data").getStr("token");
}
log.error("青龙内部登录失败: {}", json.getStr("message"));
} catch (Exception e) {
log.error("青龙内部登录异常: {}", e.getMessage());
}
return null;
}
private String getAdminAccessToken() {
if (!enabled) return null;
if (StrUtil.isNotBlank(adminToken)) {
return adminToken;
}
return doTokenRequest(clientId, clientSecret);
}
private String doTokenRequest(String cId, String cSecret) {
try {
String authUrl = url + "/open/auth/token?client_id=" + cId
+ "&client_secret=" + cSecret;
HttpResponse resp = HttpRequest.get(authUrl).timeout(10000).execute();
JSONObject json = JSONUtil.parseObj(resp.body());
if (json.getInt("code") == 200) {
return json.getJSONObject("data").getStr("token");
}
log.error("青龙token获取失败: {}", json.getStr("message"));
} catch (Exception e) {
log.error("青龙连接失败: {}", e.getMessage());
}
return null;
}
private String authHeader(Long userId) {
String token = getUserAccessToken(userId);
return token != null ? "Bearer " + token : "";
}
public String getUserAccessToken(Long userId) {
if (!enabled || userId == null) return null;
QlUserConfigEntity config = qlUserConfigService.getByUserId(userId);
if (config == null) {
config = ensureUserApp(userId);
if (config == null) return null;
}
if (StrUtil.isNotBlank(config.getAccessToken())
&& config.getTokenExpire() != null
&& System.currentTimeMillis() < config.getTokenExpire()) {
return config.getAccessToken();
}
String token = doTokenRequest(config.getClientId(), config.getClientSecret());
if (token != null) {
config.setAccessToken(token);
config.setTokenExpire(System.currentTimeMillis() + 86400_000L);
qlUserConfigService.updateById(config);
}
return token;
}
public QlUserConfigEntity ensureUserApp(Long userId) {
if (!enabled) return null;
QlUserConfigEntity existing = qlUserConfigService.getByUserId(userId);
if (existing != null && StrUtil.isNotBlank(existing.getClientId())) {
return existing;
}
String adminToken = getAdminInternalToken();
if (adminToken == null) {
log.error("无法获取管理员token来创建用户青龙应用");
return null;
}
try {
String username = "user_" + userId;
String appsUrl = url + "/api/apps?t=" + System.currentTimeMillis();
JSONObject body = new JSONObject();
body.set("name", username);
body.set("scopes", new JSONArray().put("crons").put("envs").put("scripts").put("logs"));
HttpResponse resp = HttpRequest.post(appsUrl)
.header("Authorization", "Bearer " + adminToken)
.header("Content-Type", "application/json")
.body(body.toString())
.timeout(10000).execute();
JSONObject result = JSONUtil.parseObj(resp.body());
if (result.getInt("code") == 200 && result.get("data") != null) {
JSONObject data = result.getJSONObject("data");
QlUserConfigEntity config = existing != null ? existing : new QlUserConfigEntity();
config.setUserId(userId);
config.setUsername(username);
config.setQlAppId(data.getLong("id"));
config.setClientId(data.getStr("client_id"));
config.setClientSecret(data.getStr("client_secret"));
if (config.getId() != null) {
qlUserConfigService.updateById(config);
} else {
qlUserConfigService.save(config);
}
log.info("为用户{}创建青龙应用成功: clientId={}", userId, config.getClientId());
return config;
}
log.error("创建青龙应用失败: {}", result.getStr("message"));
} catch (Exception e) {
log.error("创建青龙应用异常: {}", e.getMessage());
}
return existing;
}
public JSONObject checkConnection(Long userId) {
JSONObject result = new JSONObject();
if (!enabled) {
result.set("connected", false).set("message", "青龙未启用");
return result;
}
try {
String t = getUserAccessToken(userId);
if (t != null) {
result.set("connected", true).set("message", "连接成功");
} else {
result.set("connected", false).set("message", "Token获取失败");
}
} catch (Exception e) {
result.set("connected", false).set("message", e.getMessage());
}
return result;
}
private JSONObject qlGet(Long userId, String path) {
if (!enabled) return new JSONObject().set("code", 500).set("message", "青龙未启用");
try {
String sUrl = url + path + (path.contains("?") ? "&" : "?") + "t=" + System.currentTimeMillis();
HttpResponse resp = HttpRequest.get(sUrl)
.header("Authorization", authHeader(userId))
.timeout(10000).execute();
return JSONUtil.parseObj(resp.body());
} catch (Exception e) {
log.error("青龙API请求失败: {}", e.getMessage());
return new JSONObject().set("code", 500).set("message", e.getMessage());
}
}
private JSONObject qlPost(Long userId, String path, Object body) {
if (!enabled) return new JSONObject().set("code", 500).set("message", "青龙未启用");
try {
String sUrl = url + path + (path.contains("?") ? "&" : "?") + "t=" + System.currentTimeMillis();
HttpResponse resp = HttpRequest.post(sUrl)
.header("Authorization", authHeader(userId))
.header("Content-Type", "application/json")
.body(body instanceof String ? (String) body : body.toString())
.timeout(10000).execute();
return JSONUtil.parseObj(resp.body());
} catch (Exception e) {
log.error("青龙API请求失败: {}", e.getMessage());
return new JSONObject().set("code", 500).set("message", e.getMessage());
}
}
private JSONObject qlPut(Long userId, String path, Object body) {
if (!enabled) return new JSONObject().set("code", 500).set("message", "青龙未启用");
try {
String sUrl = url + path + (path.contains("?") ? "&" : "?") + "t=" + System.currentTimeMillis();
HttpResponse resp = HttpRequest.put(sUrl)
.header("Authorization", authHeader(userId))
.header("Content-Type", "application/json")
.body(body instanceof String ? (String) body : body.toString())
.timeout(10000).execute();
return JSONUtil.parseObj(resp.body());
} catch (Exception e) {
log.error("青龙API请求失败: {}", e.getMessage());
return new JSONObject().set("code", 500).set("message", e.getMessage());
}
}
private JSONObject qlDelete(Long userId, String path, Object body) {
if (!enabled) return new JSONObject().set("code", 500).set("message", "青龙未启用");
try {
String sUrl = url + path + (path.contains("?") ? "&" : "?") + "t=" + System.currentTimeMillis();
HttpResponse resp = HttpRequest.delete(sUrl)
.header("Authorization", authHeader(userId))
.header("Content-Type", "application/json")
.body(body instanceof String ? (String) body : body.toString())
.timeout(10000).execute();
return JSONUtil.parseObj(resp.body());
} catch (Exception e) {
log.error("青龙API请求失败: {}", e.getMessage());
return new JSONObject().set("code", 500).set("message", e.getMessage());
}
}
public JSONObject getScripts(Long userId, String searchValue, int page, int size) {
String path = "/open/scripts";
if (StrUtil.isNotBlank(searchValue)) path += "?searchValue=" + searchValue;
return qlGet(userId, path);
}
public JSONObject getScript(Long userId, String fileName) {
return qlGet(userId, "/open/scripts/" + fileName);
}
public JSONObject addScript(Long userId, JSONObject body) {
return qlPost(userId, "/open/scripts", body);
}
public JSONObject updateScript(Long userId, String fileName, JSONObject body) {
body.set("name", fileName);
return qlPut(userId, "/open/scripts", body);
}
public JSONObject deleteScript(Long userId, java.util.List<String> fileNames) {
return qlDelete(userId, "/open/scripts", JSONUtil.createArray().put(fileNames));
}
public JSONObject runScript(Long userId, JSONObject body) {
return qlPost(userId, "/open/scripts/run", body);
}
public JSONObject stopScript(Long userId, JSONObject body) {
return qlPost(userId, "/open/scripts/stop", body);
}
public JSONObject getEnvs(Long userId, String searchValue, int page, int size) {
String path = "/open/envs";
if (StrUtil.isNotBlank(searchValue)) path += "?searchValue=" + searchValue;
return qlGet(userId, path);
}
public JSONObject addEnv(Long userId, JSONArray envs) {
return qlPost(userId, "/open/envs", envs);
}
public JSONObject updateEnv(Long userId, JSONObject body) {
return qlPut(userId, "/open/envs", body);
}
public JSONObject deleteEnv(Long userId, JSONArray ids) {
return qlDelete(userId, "/open/envs", ids);
}
public JSONObject enableEnv(Long userId, JSONArray ids) {
return qlPut(userId, "/open/envs/enable", ids);
}
public JSONObject disableEnv(Long userId, JSONArray ids) {
return qlPut(userId, "/open/envs/disable", ids);
}
public JSONObject getCrons(Long userId, String searchValue, int page, int size) {
String path = "/open/crons";
if (StrUtil.isNotBlank(searchValue)) path += "?searchValue=" + searchValue;
return qlGet(userId, path);
}
public JSONObject addCron(Long userId, JSONObject body) {
return qlPost(userId, "/open/crons", body);
}
public JSONObject updateCron(Long userId, JSONObject body) {
return qlPut(userId, "/open/crons", body);
}
public JSONObject deleteCron(Long userId, JSONArray ids) {
return qlDelete(userId, "/open/crons", ids);
}
public JSONObject runCron(Long userId, JSONArray ids) {
return qlPut(userId, "/open/crons/run", ids);
}
public JSONObject stopCron(Long userId, JSONArray ids) {
return qlPut(userId, "/open/crons/stop", ids);
}
public JSONObject enableCron(Long userId, JSONArray ids) {
return qlPut(userId, "/open/crons/enable", ids);
}
public JSONObject disableCron(Long userId, JSONArray ids) {
return qlPut(userId, "/open/crons/disable", ids);
}
public JSONObject getCronLog(Long userId, String cronId) {
return qlGet(userId, "/open/crons/" + cronId + "/log");
}
}
@@ -0,0 +1,8 @@
package com.cool.modules.task.service;
import com.cool.core.base.BaseService;
import com.cool.modules.task.entity.QlUserConfigEntity;
public interface QlUserConfigService extends BaseService<QlUserConfigEntity> {
QlUserConfigEntity getByUserId(Long userId);
}
@@ -0,0 +1,7 @@
package com.cool.modules.task.service;
import com.cool.core.base.BaseService;
import com.cool.modules.task.entity.TaskGroupEntity;
public interface TaskGroupService extends BaseService<TaskGroupEntity> {
}
@@ -0,0 +1,18 @@
package com.cool.modules.task.service.impl;
import com.cool.core.base.BaseServiceImpl;
import com.cool.modules.task.entity.QlUserConfigEntity;
import com.cool.modules.task.mapper.QlUserConfigMapper;
import com.cool.modules.task.service.QlUserConfigService;
import com.mybatisflex.core.query.QueryWrapper;
import org.springframework.stereotype.Service;
@Service
public class QlUserConfigServiceImpl extends BaseServiceImpl<QlUserConfigMapper, QlUserConfigEntity>
implements QlUserConfigService {
@Override
public QlUserConfigEntity getByUserId(Long userId) {
return getOne(QueryWrapper.create().eq(QlUserConfigEntity::getUserId, userId));
}
}
@@ -0,0 +1,12 @@
package com.cool.modules.task.service.impl;
import com.cool.core.base.BaseServiceImpl;
import com.cool.modules.task.entity.TaskGroupEntity;
import com.cool.modules.task.mapper.TaskGroupMapper;
import com.cool.modules.task.service.TaskGroupService;
import org.springframework.stereotype.Service;
@Service
public class TaskGroupServiceImpl extends BaseServiceImpl<TaskGroupMapper, TaskGroupEntity>
implements TaskGroupService {
}
@@ -2,6 +2,7 @@ package com.cool.modules.task.service.impl;
import static com.cool.modules.task.entity.table.TaskInfoEntityTableDef.TASK_INFO_ENTITY;
import static com.cool.modules.task.entity.table.TaskLogEntityTableDef.TASK_LOG_ENTITY;
import static com.cool.modules.task.entity.table.TaskGroupEntityTableDef.TASK_GROUP_ENTITY;
import cn.hutool.core.convert.Convert;
import cn.hutool.json.JSONObject;
@@ -36,6 +37,9 @@ public class TaskInfoServiceImpl extends BaseServiceImpl<TaskInfoMapper, TaskInf
try {
List<TaskInfoEntity> list = list();
list.forEach(scheduleJob -> {
if (scheduleJob.getTaskType() != null && scheduleJob.getTaskType() == 2) {
return;
}
CronTrigger cronTrigger = ScheduleUtils.getCronTrigger(scheduler,
scheduleJob.getJobId());
if (cronTrigger == null) {
@@ -52,12 +56,22 @@ public class TaskInfoServiceImpl extends BaseServiceImpl<TaskInfoMapper, TaskInf
@Override
@Transactional(rollbackFor = Exception.class)
public void once(Long taskId) {
ScheduleUtils.run(scheduler, getById(taskId));
TaskInfoEntity entity = getById(taskId);
if (entity != null && entity.getTaskType() != null && entity.getTaskType() == 2) {
return;
}
ScheduleUtils.run(scheduler, entity);
}
@Override
@Transactional(rollbackFor = Exception.class)
public void stop(Long taskId) {
TaskInfoEntity entity = getById(taskId);
if (entity != null && entity.getTaskType() != null && entity.getTaskType() == 2) {
entity.setStatus(0);
updateById(entity);
return;
}
ScheduleUtils.pauseJob(scheduler, taskId + "");
TaskInfoEntity taskInfoEntity = getById(taskId);
taskInfoEntity.setStatus(0);
@@ -67,9 +81,8 @@ public class TaskInfoServiceImpl extends BaseServiceImpl<TaskInfoMapper, TaskInf
@Override
public Object log(Page page, Long taskId, Integer status) {
QueryWrapper queryWrapper = QueryWrapper.create().select(TASK_LOG_ENTITY.DETAIL,
TASK_LOG_ENTITY.STATUS, TASK_LOG_ENTITY.CREATE_TIME,
TASK_LOG_ENTITY.STATUS, TASK_LOG_ENTITY.CREATE_TIME, TASK_LOG_ENTITY.DURATION,
TASK_INFO_ENTITY.NAME).from(TASK_LOG_ENTITY)
.leftJoin(TASK_INFO_ENTITY).on(TASK_LOG_ENTITY.TASK_ID.eq(TASK_INFO_ENTITY.ID))
.eq(TaskLogEntity::getTaskId, taskId, taskId != null)
@@ -86,6 +99,10 @@ public class TaskInfoServiceImpl extends BaseServiceImpl<TaskInfoMapper, TaskInf
if (type != null) {
taskInfoEntity.setType(type);
}
if (taskInfoEntity.getTaskType() != null && taskInfoEntity.getTaskType() == 2) {
updateById(taskInfoEntity);
return;
}
boolean isExists = false;
try {
isExists = scheduler.checkExists(ScheduleUtils.getJobKey(taskId + ""));
@@ -100,16 +117,20 @@ public class TaskInfoServiceImpl extends BaseServiceImpl<TaskInfoMapper, TaskInf
}
updateById(taskInfoEntity);
modifyAfter(JSONUtil.parseObj(taskInfoEntity), taskInfoEntity);
}
@Override
@Transactional(rollbackFor = Exception.class)
public Long add(JSONObject requestParams, TaskInfoEntity scheduleJob) {
if (scheduleJob.getTaskType() != null && scheduleJob.getTaskType() == 2) {
scheduleJob.setStatus(1);
super.add(scheduleJob);
super.modifyAfter(requestParams, scheduleJob);
return scheduleJob.getId();
}
scheduleJob.setStatus(1);
super.add(scheduleJob);
scheduleJob.setJobId(scheduleJob.getId() + "");
ScheduleUtils.createScheduleJob(scheduler, scheduleJob);
updateById(scheduleJob);
super.modifyAfter(requestParams, scheduleJob);
@@ -118,6 +139,10 @@ public class TaskInfoServiceImpl extends BaseServiceImpl<TaskInfoMapper, TaskInf
@Override
public boolean update(JSONObject requestParams, TaskInfoEntity entity) {
if (entity.getTaskType() != null && entity.getTaskType() == 2) {
updateById(entity);
return true;
}
updateById(entity);
ScheduleUtils.deleteScheduleJob(scheduler, entity.getId().toString());
if (entity.getStatus() == 1) {
@@ -131,9 +156,12 @@ public class TaskInfoServiceImpl extends BaseServiceImpl<TaskInfoMapper, TaskInf
@Override
@Transactional(rollbackFor = Exception.class)
public boolean delete(JSONObject requestParams, Long... ids) {
Convert.toList(String.class, ids).forEach(jobId -> {
ScheduleUtils.deleteScheduleJob(scheduler, jobId);
});
for (Long id : ids) {
TaskInfoEntity entity = getById(id);
if (entity != null && entity.getTaskType() != null && entity.getTaskType() != 2) {
ScheduleUtils.deleteScheduleJob(scheduler, id.toString());
}
}
return super.delete(requestParams, ids);
}
}
+12 -1
View File
@@ -38,4 +38,15 @@ springdoc:
logging:
level:
com.cool: info
com.cool: info
# 青龙面板配置
qinglong:
enabled: true
url: http://172.18.0.24:5700
client-id: 1ADIHKg91O-d
client-secret: BvUc-gDP56T-OOId20dUWrNm
admin-username: wol
admin-password: "123456"
admin-token:
token:
+12 -1
View File
@@ -162,4 +162,15 @@ auto-table:
leaf:
segment:
# 默认禁用
enable: false
enable: false
# 青龙面板配置
qinglong:
enabled: false
url: http://172.18.0.24:5700
client-id: 1ADIHKg91O-d
client-secret: BvUc-gDP56T-OOId20dUWrNm
admin-username: wol
admin-password: "123456"
admin-token:
token:
+22 -2
View File
@@ -110,12 +110,32 @@
"status": 1,
"service": "baseSysLogServiceImpl.clear(false)",
"task_type": 0,
"type": null,
"type": 0,
"data": null,
"remark": null,
"cron": "0 0 1 * * ?",
"start_date": null,
"end_date": null
"end_date": null,
"group_id": 1,
"ql_script_id": null,
"ql_script_name": null,
"last_duration": null,
"last_run_time": null,
"fail_count": 0
}
],
"task_group": [
{
"id": 1,
"name": "系统任务",
"order_num": 0,
"remark": "系统内置定时任务"
},
{
"id": 2,
"name": "青龙脚本",
"order_num": 1,
"remark": "青龙面板脚本任务"
}
]
}
+101 -1
View File
@@ -420,7 +420,7 @@
"type": 1,
"icon": "icon-menu",
"orderNum": 0,
"viewPath": "modules/task/views/list.vue",
"viewPath": "cool/modules/task/views/list.vue",
"keepAlive": true,
"isShow": true,
"childMenus": [
@@ -437,6 +437,106 @@
"childMenus": []
}
]
},
{
"name": "任务分组",
"router": "/task/group",
"perms": null,
"type": 1,
"icon": "icon-favor",
"orderNum": 1,
"viewPath": "cool/modules/task/views/group.vue",
"keepAlive": true,
"isShow": true,
"childMenus": [
{
"name": "权限",
"router": null,
"perms": "task:group:page,task:group:list,task:group:info,task:group:add,task:group:delete,task:group:update",
"type": 2,
"icon": null,
"orderNum": 0,
"viewPath": null,
"keepAlive": true,
"isShow": true,
"childMenus": []
}
]
},
{
"name": "青龙脚本",
"router": "/task/ql-script",
"perms": null,
"type": 1,
"icon": "icon-log",
"orderNum": 2,
"viewPath": "cool/modules/task/views/ql-script.vue",
"keepAlive": true,
"isShow": true,
"childMenus": [
{
"name": "权限",
"router": null,
"perms": "task:qinglong:scripts,task:qinglong:run,task:qinglong:stop,task:qinglong:add,task:qinglong:delete",
"type": 2,
"icon": null,
"orderNum": 0,
"viewPath": null,
"keepAlive": true,
"isShow": true,
"childMenus": []
}
]
},
{
"name": "青龙定时",
"router": "/task/ql-cron",
"perms": null,
"type": 1,
"icon": "icon-task",
"orderNum": 3,
"viewPath": "cool/modules/task/views/ql-cron.vue",
"keepAlive": true,
"isShow": true,
"childMenus": [
{
"name": "权限",
"router": null,
"perms": "task:qinglong:crons,task:qinglong:cronRun,task:qinglong:cronStop,task:qinglong:cronAdd,task:qinglong:cronDelete,task:qinglong:cronLog",
"type": 2,
"icon": null,
"orderNum": 0,
"viewPath": null,
"keepAlive": true,
"isShow": true,
"childMenus": []
}
]
},
{
"name": "环境变量",
"router": "/task/ql-env",
"perms": null,
"type": 1,
"icon": "icon-params",
"orderNum": 4,
"viewPath": "cool/modules/task/views/ql-env.vue",
"keepAlive": true,
"isShow": true,
"childMenus": [
{
"name": "权限",
"router": null,
"perms": "task:qinglong:envs,task:qinglong:envAdd,task:qinglong:envUpdate,task:qinglong:envDelete,task:qinglong:envEnable,task:qinglong:envDisable",
"type": 2,
"icon": null,
"orderNum": 0,
"viewPath": null,
"keepAlive": true,
"isShow": true,
"childMenus": []
}
]
}
]
}