新增缓存管理查看定时任务执行统计

This commit is contained in:
bwcx_jzy 2022-01-23 08:41:25 +08:00
parent d76475c55b
commit 95958d9e69
No known key found for this signature in database
GPG Key ID: 5E48E9372088B9E5
9 changed files with 263 additions and 65 deletions

View File

@ -9,6 +9,7 @@
3. 【server】新增系统配置-配置菜单是否显示,用于非超级管理员页面菜单控制
4. 【server】新增节点统计功能快速了解当前所有节点状态
5. 【server】新增节点心跳检测配置`system.nodeHeartSecond`
6. 新增缓存管理查看定时任务执行统计
### 解决BUG、优化功能

View File

@ -22,14 +22,20 @@
*/
package io.jpom.cron;
import cn.hutool.core.date.SystemClock;
import cn.hutool.cron.CronUtil;
import cn.hutool.cron.Scheduler;
import cn.hutool.cron.TaskExecutor;
import cn.hutool.cron.TaskTable;
import cn.hutool.cron.listener.TaskListener;
import cn.hutool.cron.task.Task;
import cn.jiangzeyin.common.DefaultSystemLog;
import com.alibaba.fastjson.JSONObject;
import io.jpom.system.ExtConfigBean;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Supplier;
import java.util.stream.Collectors;
@ -39,6 +45,30 @@ import java.util.stream.Collectors;
**/
public class CronUtils {
private static final Map<String, TaskStat> TASK_STAT = new ConcurrentHashMap<>(50);
/**
* 任务统计
*/
private static class TaskStat {
/**
* 执行次数
*/
private int executeCount;
/**
* 失败次数
*/
private int failedCount;
/**
* 成功次数
*/
private int succeedCount;
/**
* 最后执行时间
*/
private Long lastExecuteTime;
}
/**
* 开始
*/
@ -50,6 +80,27 @@ public class CronUtils {
Scheduler scheduler = CronUtil.getScheduler();
if (!scheduler.isStarted()) {
CronUtil.start();
scheduler.addListener(new TaskListener() {
@Override
public void onStart(TaskExecutor executor) {
TaskStat taskStat = TASK_STAT.computeIfAbsent(executor.getCronTask().getId(), s -> new TaskStat());
taskStat.lastExecuteTime = SystemClock.now();
taskStat.executeCount++;
}
@Override
public void onSucceeded(TaskExecutor executor) {
TaskStat taskStat = TASK_STAT.computeIfAbsent(executor.getCronTask().getId(), s -> new TaskStat());
taskStat.succeedCount++;
}
@Override
public void onFailed(TaskExecutor executor, Throwable exception) {
TaskStat taskStat = TASK_STAT.computeIfAbsent(executor.getCronTask().getId(), s -> new TaskStat());
taskStat.failedCount++;
DefaultSystemLog.getLog().error("定时任务异常", exception);
}
});
}
}
@ -63,9 +114,16 @@ public class CronUtils {
TaskTable taskTable = scheduler.getTaskTable();
List<String> ids = taskTable.getIds();
return ids.stream().map(s -> {
TaskStat taskStat = TASK_STAT.get(s);
JSONObject jsonObject = new JSONObject();
jsonObject.put("taskId", s);
jsonObject.put("cron", scheduler.getPattern(s).toString());
if (taskStat != null) {
jsonObject.put("executeCount", taskStat.executeCount);
jsonObject.put("failedCount", taskStat.failedCount);
jsonObject.put("succeedCount", taskStat.succeedCount);
jsonObject.put("lastExecuteTime", taskStat.lastExecuteTime);
}
return jsonObject;
}).collect(Collectors.toList());
}

View File

@ -22,7 +22,6 @@
*/
package io.jpom.controller.monitor;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.util.StrUtil;
import cn.jiangzeyin.common.JsonMessage;
import cn.jiangzeyin.common.validator.ValidatorConfig;
@ -45,7 +44,6 @@ import org.springframework.http.MediaType;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
@ -84,7 +82,6 @@ public class MonitorListController extends BaseServerController {
* @return json
*/
@RequestMapping(value = "getMonitorList", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
@Feature(method = MethodFeature.LIST)
public String getMonitorList() {
PageResultDto<MonitorModel> pageResultDto = monitorService.listPage(getRequest());
@ -98,7 +95,6 @@ public class MonitorListController extends BaseServerController {
* @return json
*/
@RequestMapping(value = "deleteMonitor", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
@Feature(method = MethodFeature.DEL)
public String deleteMonitor(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "删除失败") String id) throws SQLException {
//
@ -121,7 +117,6 @@ public class MonitorListController extends BaseServerController {
* @return json
*/
@RequestMapping(value = "updateMonitor", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
@Feature(method = MethodFeature.EDIT)
public String updateMonitor(String id,
@ValidatorConfig(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "监控名称不能为空")) String name,
@ -170,33 +165,32 @@ public class MonitorListController extends BaseServerController {
return JsonMessage.getString(200, "修改成功");
}
/**
* 开启或关闭监控
*
* @param id id
* @param status 状态
* @param type 类型
* @return json
*/
@RequestMapping(value = "changeStatus", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
@ResponseBody
@Feature(method = MethodFeature.EDIT)
public String changeStatus(@ValidatorConfig(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "id不能为空")) String id,
String status, String type) {
MonitorModel monitorModel = monitorService.getByKey(id);
Assert.notNull(monitorModel, "不存在监控项啦");
boolean bStatus = Convert.toBool(status, false);
if ("status".equalsIgnoreCase(type)) {
monitorModel.setStatus(bStatus);
} else if ("restart".equalsIgnoreCase(type)) {
monitorModel.setAutoRestart(bStatus);
} else {
return JsonMessage.getString(405, "type不正确");
}
monitorService.updateById(monitorModel);
return JsonMessage.getString(200, "修改成功");
}
// /**
// * 开启或关闭监控
// *
// * @param id id
// * @param status 状态
// * @param type 类型
// * @return json
// */
// @RequestMapping(value = "changeStatus", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
// @Feature(method = MethodFeature.EDIT)
// public String changeStatus(@ValidatorConfig(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "id不能为空")) String id,
// String status, String type) {
// MonitorModel monitorModel = monitorService.getByKey(id);
// Assert.notNull(monitorModel, "不存在监控项啦");
//
// boolean bStatus = Convert.toBool(status, false);
// if ("status".equalsIgnoreCase(type)) {
// monitorModel.setStatus(bStatus);
// } else if ("restart".equalsIgnoreCase(type)) {
// monitorModel.setAutoRestart(bStatus);
// } else {
// return JsonMessage.getString(405, "type不正确");
// }
// monitorService.updateById(monitorModel);
// return JsonMessage.getString(200, "修改成功");
// }
}

View File

@ -38,6 +38,7 @@ import java.util.List;
*/
@TableName(value = "MONITOR_INFO", name = "监控信息")
public class MonitorModel extends BaseWorkspaceModel {
private String name;
/**
* 监控的项目
@ -54,6 +55,7 @@ public class MonitorModel extends BaseWorkspaceModel {
/**
* 监控周期
*/
@Deprecated
private Integer cycle;
/**
@ -73,11 +75,12 @@ public class MonitorModel extends BaseWorkspaceModel {
this.name = name;
}
@Deprecated
public Integer getCycle() {
return cycle;
}
@Deprecated
public void setCycle(Integer cycle) {
this.cycle = cycle;
}

View File

@ -14,8 +14,8 @@
<a-tooltip slot="name" slot-scope="text" placement="topLeft" :title="text">
<span>{{ text }}</span>
</a-tooltip>
<a-switch slot="status" slot-scope="text" :checked="text" checked-children="开启" un-checked-children="关闭" />
<a-switch slot="autoRestart" slot-scope="text" :checked="text" checked-children="" un-checked-children="" />
<a-switch slot="status" slot-scope="text" :checked="text" disabled checked-children="开启" un-checked-children="关闭" />
<a-switch slot="autoRestart" slot-scope="text" :checked="text" disabled checked-children="" un-checked-children="" />
<a-switch slot="alarm" slot-scope="text" :checked="text" disabled checked-children="报警中" un-checked-children="未报警" />
<a-tooltip slot="parent" slot-scope="text" placement="topLeft" :title="text">
<span>{{ text }}</span>
@ -33,12 +33,21 @@
<a-form-model-item label="监控名称" prop="name">
<a-input v-model="temp.name" placeholder="监控名称" />
</a-form-model-item>
<a-form-model-item label="开启状态" prop="status">
<a-switch v-model="temp.status" checked-children="" un-checked-children="" />
</a-form-model-item>
<a-form-model-item label="自动重启" prop="autoRestart">
<a-switch v-model="temp.autoRestart" checked-children="" un-checked-children="" />
<a-space size="large">
<a-switch v-model="temp.status" checked-children="" un-checked-children="" />
<div>
自动重启:
<a-switch v-model="temp.autoRestart" checked-children="" un-checked-children="" />
</div>
</a-space>
</a-form-model-item>
<!-- <a-form-model-item label="自动重启" prop="autoRestart">
</a-form-model-item> -->
<a-form-model-item label="监控周期" prop="cycle">
<a-radio-group v-model="temp.cycle" name="cycle">
<a-radio :value="1">1 分钟</a-radio>
@ -54,7 +63,7 @@
</a-form-model-item>
<a-form-model-item prop="notifyUser" class="jpom-notify">
<template slot="label">
报警联系人
联系人
<a-tooltip v-show="!temp.id">
<template slot="title"> 如果这里的报警联系人无法选择说明这里面的管理员没有设置邮箱在右上角下拉菜单里面的用户资料里可以设置 </template>
<a-icon type="question-circle" theme="filled" />
@ -337,7 +346,4 @@ export default {
};
</script>
<style scoped>
.filter {
margin-bottom: 10px;
}
</style>

View File

@ -33,22 +33,21 @@
</a-timeline-item>
<a-timeline-item>
<span class="layui-elem-quote">运行中的定时任务</span>
<a-list v-if="taskList && taskList.length" bordered :data-source="taskList">
<a-list-item slot="renderItem" slot-scope="item">
<a-list-item-meta :description="item.taskId">
<a slot="title"> {{ item.id }}</a>
</a-list-item-meta>
<div>
{{ item.cron }}
</div>
</a-list-item>
</a-list>
<a-table rowKey="taskId" :columns="taskColumns" :data-source="taskList" :pagination="false">
<a-tooltip slot="tooltip" slot-scope="text" placement="topLeft" :title="text">
<span>{{ text }}</span>
</a-tooltip>
<a-tooltip slot="time" slot-scope="text" placement="topLeft" :title="parseTime(text)">
<span>{{ parseTime(text) }}</span>
</a-tooltip>
</a-table>
</a-timeline-item>
</a-timeline>
</div>
</template>
<script>
import { getNodeCache, clearCache } from "@/api/system";
import { parseTime } from "@/utils/time";
export default {
props: {
node: {
@ -59,12 +58,74 @@ export default {
return {
temp: {},
taskList: [],
taskColumns: [
{
title: "任务ID",
dataIndex: "taskId",
defaultSortOrder: "descend",
sorter: (a, b) => a.taskId - b.taskId,
ellipsis: true,
scopedSlots: { customRender: "tooltip" },
filters: [
{
text: "构建",
value: "build",
},
{
text: "节点脚本",
value: "script",
},
{
text: "服务端脚本",
value: "server_script",
},
{
text: "ssh 脚本",
value: "ssh_command",
},
],
onFilter: (value, record) => record.taskId.indexOf(value) === 0,
},
{
title: "cron",
dataIndex: "cron",
sorter: (a, b) => a.cron.length - b.cron.length,
sortDirections: ["descend", "ascend"],
},
{
title: "执行次数",
dataIndex: "executeCount",
sortDirections: ["descend", "ascend"],
sorter: (a, b) => a.executeCount || 0 - b.executeCount || 0,
},
{
title: "成功次数",
dataIndex: "succeedCount",
sortDirections: ["descend", "ascend"],
sorter: (a, b) => a.succeedCount || 0 - b.succeedCount || 0,
},
{
title: "失败次数",
dataIndex: "failedCount",
sortDirections: ["descend", "ascend"],
sorter: (a, b) => a.failedCount || 0 - b.failedCount || 0,
},
{
title: "最后一次执行时间",
dataIndex: "lastExecuteTime",
sortDirections: ["descend", "ascend"],
sorter: (a, b) => a.lastExecuteTime || 0 - b.lastExecuteTime || 0,
ellipsis: true,
scopedSlots: { customRender: "time" },
},
],
};
},
mounted() {
this.loadData();
},
methods: {
parseTime: parseTime,
// load data
loadData() {
getNodeCache(this.node.id).then((res) => {

View File

@ -96,7 +96,13 @@ export default {
return {
loading: false,
statusMap: status,
listQuery: Object.assign({}, PAGE_DEFAULT_LIST_QUERY),
listQuery: Object.assign(
{
order: "descend",
order_field: "networkTime",
},
PAGE_DEFAULT_LIST_QUERY
),
list: [],
statusStatMap: {},
openStatusMap: {},
@ -111,7 +117,7 @@ export default {
{ title: "disk", dataIndex: "occupyDisk", sorter: true, key: "occupyDisk", ellipsis: true, scopedSlots: { customRender: "progress" } },
{ title: "memory", dataIndex: "occupyMemory", sorter: true, key: "occupyMemory", ellipsis: true, scopedSlots: { customRender: "progress" } },
{ title: "memoryUsed", dataIndex: "occupyMemoryUsed", sorter: true, key: "occupyMemoryUsed", ellipsis: true, scopedSlots: { customRender: "progress" } },
{ title: "延迟(ms)", width: 100, dataIndex: "networkTime", sorter: true, key: "networkTime", ellipsis: true, scopedSlots: { customRender: "tooltipStatus" } },
{ title: "延迟(ms)", width: 100, dataIndex: "networkTime", defaultSortOrder: "descend", sorter: true, key: "networkTime", ellipsis: true, scopedSlots: { customRender: "tooltipStatus" } },
{ title: "运行时间", dataIndex: "upTimeStr", sorter: true, key: "upTimeStr", ellipsis: true, scopedSlots: { customRender: "tooltipStatus" } },
{ title: "状态", width: 100, dataIndex: "status", sorter: true, key: "status", ellipsis: true, scopedSlots: { customRender: "status" } },
{

View File

@ -28,33 +28,94 @@
</a-timeline-item>
<a-timeline-item>
<span class="layui-elem-quote">运行中的定时任务</span>
<a-list v-if="taskList && taskList.length" bordered :data-source="taskList">
<a-list-item slot="renderItem" slot-scope="item">
<a-list-item-meta :description="item.taskId">
<a slot="title"> {{ item.id }}</a>
</a-list-item-meta>
<div>
{{ item.cron }}
</div>
</a-list-item>
</a-list>
<a-table rowKey="taskId" :columns="taskColumns" :data-source="taskList" :pagination="false">
<a-tooltip slot="tooltip" slot-scope="text" placement="topLeft" :title="text">
<span>{{ text }}</span>
</a-tooltip>
<a-tooltip slot="time" slot-scope="text" placement="topLeft" :title="parseTime(text)">
<span>{{ parseTime(text) }}</span>
</a-tooltip>
</a-table>
</a-timeline-item>
</a-timeline>
</div>
</template>
<script>
import { getServerCache, clearCache } from "@/api/system";
import { parseTime } from "@/utils/time";
export default {
data() {
return {
temp: {},
taskList: [],
taskColumns: [
{
title: "任务ID",
dataIndex: "taskId",
defaultSortOrder: "descend",
sorter: (a, b) => a.localeCompare(b, "zh-CN"),
scopedSlots: { customRender: "tooltip" },
ellipsis: true,
filters: [
{
text: "构建",
value: "build",
},
{
text: "节点脚本",
value: "script",
},
{
text: "服务端脚本",
value: "server_script",
},
{
text: "ssh 脚本",
value: "ssh_command",
},
],
onFilter: (value, record) => record.taskId.indexOf(value) === 0,
},
{
title: "cron",
dataIndex: "cron",
sorter: (a, b) => a.localeCompare(b, "zh-CN"),
sortDirections: ["descend", "ascend"],
},
{
title: "执行次数",
dataIndex: "executeCount",
sortDirections: ["descend", "ascend"],
sorter: (a, b) => a.executeCount || 0 - b.executeCount || 0,
},
{
title: "成功次数",
dataIndex: "succeedCount",
sortDirections: ["descend", "ascend"],
sorter: (a, b) => a.succeedCount || 0 - b.succeedCount || 0,
},
{
title: "失败次数",
dataIndex: "failedCount",
sortDirections: ["descend", "ascend"],
sorter: (a, b) => a.failedCount || 0 - b.failedCount || 0,
},
{
title: "最后一次执行时间",
dataIndex: "lastExecuteTime",
sortDirections: ["descend", "ascend"],
sorter: (a, b) => a.lastExecuteTime || 0 - b.lastExecuteTime || 0,
scopedSlots: { customRender: "time" },
},
],
};
},
mounted() {
this.loadData();
// console.log(Comparator);
},
methods: {
parseTime: parseTime,
// load data
loadData() {
getServerCache().then((res) => {

View File

@ -84,6 +84,10 @@ export const CRON_DATA_SOURCE = [
title: "1分钟",
value: "0 0/1 * * * ?",
},
{
title: "5分钟",
value: "0 0/5 * * * ?",
},
{
title: "10分钟",
value: "0 0/10 * * * ?",
@ -127,6 +131,10 @@ export const CRON_DATA_SOURCE = [
title: "10秒一次",
value: "0/10 * * * * ?",
},
{
title: "30秒一次",
value: "0/30 * * * * ?",
},
],
},
];