forked from github/cool-admin-java
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:
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
@@ -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:
|
||||
@@ -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": "青龙面板脚本任务"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -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": []
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user