feta 触发器调用次数统计、触发器统一管理

This commit is contained in:
bwcx_jzy 2024-01-17 16:00:46 +08:00
parent 483f05b33a
commit 0cf80417f9
No known key found for this signature in database
GPG Key ID: E187D6E9DDDE8C53
11 changed files with 414 additions and 4 deletions

View File

@ -2,6 +2,10 @@
## 2.11.1.2-beta
### 🐣 新增功能
1. 【server】新增 触发器调用次数统计、触发器统一管理
### 🐞 解决BUG、优化功能
1. 【all】优化 机器状态新增:资源监控异常(资源监控异常不影响功能使用)

View File

@ -20,7 +20,7 @@
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package org.dromara.jpom.controller.system;
package org.dromara.jpom.func.system.controller;
import cn.hutool.cache.impl.CacheObj;
import cn.hutool.cache.impl.LFUCache;

View File

@ -0,0 +1,85 @@
package org.dromara.jpom.func.system.controller;
import cn.hutool.core.bean.BeanUtil;
import cn.keepbx.jpom.IJsonMessage;
import cn.keepbx.jpom.model.BaseIdModel;
import cn.keepbx.jpom.model.JsonMessage;
import com.alibaba.fastjson2.JSONObject;
import org.dromara.jpom.model.PageResultDto;
import org.dromara.jpom.model.user.TriggerTokenLogBean;
import org.dromara.jpom.permission.ClassFeature;
import org.dromara.jpom.permission.Feature;
import org.dromara.jpom.permission.MethodFeature;
import org.dromara.jpom.permission.SystemPermission;
import org.dromara.jpom.service.ITriggerToken;
import org.dromara.jpom.service.user.TriggerTokenLogServer;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.util.List;
/**
* @author bwcx_jzy
* @since 24/1/17 017
*/
@RestController
@RequestMapping(value = "system/trigger")
@Feature(cls = ClassFeature.SYSTEM_CACHE)
@SystemPermission
public class TriggerTokenController {
private final TriggerTokenLogServer triggerTokenLogServer;
public TriggerTokenController(TriggerTokenLogServer triggerTokenLogServer) {
this.triggerTokenLogServer = triggerTokenLogServer;
}
@GetMapping(value = "all-type", produces = MediaType.APPLICATION_JSON_VALUE)
@Feature(method = MethodFeature.LIST)
public IJsonMessage<List<JSONObject>> allType() {
List<JSONObject> jsonObjects = triggerTokenLogServer.allType();
return JsonMessage.success("", jsonObjects);
}
@GetMapping(value = "delete", produces = MediaType.APPLICATION_JSON_VALUE)
@Feature(method = MethodFeature.LIST)
public IJsonMessage<String> delete(String id) {
triggerTokenLogServer.delete(id);
return JsonMessage.success("删除成功");
}
/**
* 分页列表
*
* @return json
*/
@PostMapping(value = "/list", produces = MediaType.APPLICATION_JSON_VALUE)
@Feature(method = MethodFeature.LIST)
public IJsonMessage<PageResultDto<TriggerTokenLogBean>> list(HttpServletRequest request) {
PageResultDto<TriggerTokenLogBean> listPage = triggerTokenLogServer.listPage(request);
listPage.each(triggerTokenLogBean -> {
String type = triggerTokenLogBean.getType();
ITriggerToken byType = triggerTokenLogServer.getByType(type);
if (byType == null) {
triggerTokenLogBean.setDataName("ERROR:类型不存在" + type);
} else {
BaseIdModel byKey = byType.getByKey(triggerTokenLogBean.getDataId());
if (byKey == null) {
triggerTokenLogBean.setDataName("ERROR:关联数据丢失");
} else {
Object name = BeanUtil.getProperty(byKey, "name");
if (name == null) {
triggerTokenLogBean.setDataName("ERROR:关联数据名称不存在");
} else {
triggerTokenLogBean.setDataName(name.toString());
}
}
}
});
return JsonMessage.success("", listPage);
}
}

View File

@ -22,6 +22,7 @@
*/
package org.dromara.jpom.model.user;
import cn.hutool.core.annotation.PropIgnore;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.jpom.db.TableName;
@ -55,6 +56,11 @@ public class TriggerTokenLogBean extends BaseDbModel {
* 关联数据ID
*/
private String dataId;
/**
* 关联数据名称
*/
@PropIgnore
private String dataName;
/**
* 用户ID
@ -62,4 +68,8 @@ public class TriggerTokenLogBean extends BaseDbModel {
* @see UserModel#getId()
*/
private String userId;
/**
* 触发次数
*/
private Integer triggerCount;
}

View File

@ -22,6 +22,8 @@
*/
package org.dromara.jpom.service;
import cn.keepbx.jpom.model.BaseIdModel;
/**
* 带有触发器 token 相关实现服务
*
@ -37,6 +39,13 @@ public interface ITriggerToken {
*/
String typeName();
/**
* 数据描述
*
* @return 描述
*/
String getDataDesc();
/**
* 判断是否存在
*
@ -45,4 +54,5 @@ public interface ITriggerToken {
*/
boolean exists(String dataId);
BaseIdModel getByKey(String keyValue);
}

View File

@ -22,14 +22,17 @@
*/
package org.dromara.jpom.service.user;
import cn.hutool.core.collection.CollStreamUtil;
import cn.hutool.core.date.BetweenFormatter;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.SystemClock;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.db.Entity;
import cn.hutool.db.Page;
import cn.keepbx.jpom.event.ISystemTask;
import com.alibaba.fastjson2.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.dromara.jpom.model.PageResultDto;
import org.dromara.jpom.model.user.TriggerTokenLogBean;
@ -41,6 +44,8 @@ import org.springframework.util.Assert;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
* @author bwcx_jzy
@ -52,11 +57,58 @@ public class TriggerTokenLogServer extends BaseDbService<TriggerTokenLogBean> im
private final UserService userService;
private final List<ITriggerToken> triggerTokens;
private final Map<String, ITriggerToken> triggerTokenMap;
public TriggerTokenLogServer(UserService userService,
List<ITriggerToken> triggerTokens) {
this.userService = userService;
this.triggerTokens = triggerTokens;
triggerTokenMap = CollStreamUtil.toMap(triggerTokens, ITriggerToken::typeName, iTriggerToken -> iTriggerToken);
}
/**
* 获取类型
*
* @param type 类型名称
* @return 接口
*/
public ITriggerToken getByType(String type) {
return MapUtil.get(triggerTokenMap, type, ITriggerToken.class);
}
/**
* 删除触发器
*
* @param id Id
*/
public void delete(String id) {
TriggerTokenLogBean tokenLogBean = this.getByKey(id);
if (tokenLogBean == null) {
return;
}
ITriggerToken token = triggerTokens.stream()
.filter(iTriggerToken -> StrUtil.equals(iTriggerToken.typeName(), tokenLogBean.getType()))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("没有对应的触发器类型:" + tokenLogBean.getType()));
String sql = "update " + tokenLogBean.getType() + " set triggerToken='' where id=?";
this.execute(sql, tokenLogBean.getDataId());
this.delByKey(id);
}
/**
* 获取所有类型
*
* @return list
*/
public List<JSONObject> allType() {
return triggerTokens.stream()
.map(iTriggerToken -> {
JSONObject jsonObject = new JSONObject();
jsonObject.put("name", iTriggerToken.typeName());
jsonObject.put("desc", iTriggerToken.getDataDesc());
return jsonObject;
})
.collect(Collectors.toList());
}
/**
@ -84,6 +136,9 @@ public class TriggerTokenLogServer extends BaseDbService<TriggerTokenLogBean> im
if (userModel != null && StrUtil.equals(type, tokenLogBean.getType())) {
boolean demoUser = userModel.isDemoUser();
Assert.state(!demoUser, "当前用户触发器不可用");
// 修改触发次数
String sql = "update " + this.getTableName() + " set triggerCount=ifnull(triggerCount,0)+1 where id=?";
int execute = this.execute(sql, tokenLogBean.getId());
return userModel;
}
}

View File

@ -13,3 +13,4 @@ ADD,NODE_INFO,jpomScriptCount,Integer,,,jpom脚本数,
ALTER,STATIC_FILE_STORAGE,parentAbsolutePath,String,300,,父级文件路径,false
ALTER,STATIC_FILE_STORAGE,absolutePath,String,300,,文件路径,false
ALTER,STATIC_FILE_STORAGE,name,String,100,,文件名,false
ADD,TRIGGER_TOKEN_LOG,triggerCount,Integer,,,触发次数

1 alterType,tableName,name,type,len,defaultValue,comment,notNull
13 ALTER,STATIC_FILE_STORAGE,parentAbsolutePath,String,300,,父级文件路径,false
14 ALTER,STATIC_FILE_STORAGE,absolutePath,String,300,,文件路径,false
15 ALTER,STATIC_FILE_STORAGE,name,String,100,,文件名,false
16 ADD,TRIGGER_TOKEN_LOG,triggerCount,Integer,,,触发次数

View File

@ -83,6 +83,12 @@ public abstract class BaseDbCommonService<T> {
this.tableName = annotation.value();
}
public String getDataDesc() {
TableName annotation = tClass.getAnnotation(TableName.class);
Assert.notNull(annotation, "请配置 table Name");
return annotation.name();
}
protected DataSource getDataSource() {
DSFactory dsFactory = StorageServiceFactory.get().getDsFactory();
return dsFactory.getDataSource();

View File

@ -0,0 +1,25 @@
import axios from './config'
export function triggerTokenList(data) {
return axios({
url: '/system/trigger/list',
method: 'post',
data: data
})
}
export function triggerTokenAllType(data) {
return axios({
url: '/system/trigger/all-type',
method: 'get',
params: data
})
}
export function triggerTokenDelete(data) {
return axios({
url: '/system/trigger/delete',
method: 'get',
params: data
})
}

View File

@ -149,18 +149,23 @@
</a-timeline> -->
</a-tab-pane>
<a-tab-pane key="2" tab="运行中的定时任务" force-render>
<task-stat :taskList="taskList" @refresh="loadData"
/></a-tab-pane>
<task-stat :taskList="taskList" @refresh="loadData" />
</a-tab-pane>
<a-tab-pane key="3" tab="触发器管理">
<TriggerToken />
</a-tab-pane>
</a-tabs>
</div>
</template>
<script>
import { getServerCache, clearCache, clearErrorWorkspace } from '@/api/system'
import TaskStat from '@/pages/system/taskStat'
import TriggerToken from '@/pages/system/trigger-token'
import { renderSize, formatDuration } from '@/utils/const'
export default {
components: {
TaskStat
TaskStat,
TriggerToken
},
data() {
return {

View File

@ -0,0 +1,209 @@
<template>
<div>
<!-- 数据表格 -->
<a-table
:data-source="viewOperationLogList"
:loading="viewOperationLoading"
:columns="viewOperationLogColumns"
:pagination="viewOperationLogPagination"
@change="changeListLog"
bordered
size="middle"
:scroll="{
x: 'max-content'
}"
>
<template v-slot:title>
<a-space>
<a-input
class="search-input-item"
@pressEnter="handleListLog"
v-model:value="viewOperationLogListQuery['userId']"
placeholder="创建人,全匹配"
/>
<a-input
class="search-input-item"
@pressEnter="handleListLog"
v-model:value="viewOperationLogListQuery['triggerToken']"
placeholder="token,全匹配"
/>
<a-select
v-model:value="viewOperationLogListQuery.type"
allowClear
placeholder="类型"
class="search-input-item"
>
<a-select-option v-for="item in allTypeList" :key="item.name">{{ item.desc }}</a-select-option>
</a-select>
<a-range-picker
:show-time="{ format: 'HH:mm:ss' }"
format="YYYY-MM-DD HH:mm:ss"
@change="onchangeListLogTime"
/>
<a-button type="primary" @click="handleListLog">搜索</a-button>
</a-space>
</template>
<template #bodyCell="{ column, text, record }">
<template v-if="column.dataIndex === 'commands'">
<a-tooltip placement="topLeft" :title="text">
<a-typography-paragraph
v-if="text"
:copyable="{ tooltip: false, text: text }"
style="display: inline-block; margin-bottom: 0"
>
</a-typography-paragraph>
{{ text }}
</a-tooltip>
</template>
<template v-else-if="column.tooltip">
<a-tooltip placement="topLeft" :title="text">
<span>{{ text }}</span>
</a-tooltip>
</template>
<template v-else-if="column.dataIndex === 'operation'">
<a-space>
<a-button size="small" type="primary" danger @click="handleDelete(record)">删除</a-button>
</a-space>
</template>
</template>
</a-table>
</div>
</template>
<script>
import { getSshOperationLogList } from '@/api/ssh'
import { CHANGE_PAGE, COMPUTED_PAGINATION, PAGE_DEFAULT_LIST_QUERY, parseTime } from '@/utils/const'
import { triggerTokenList, triggerTokenAllType, triggerTokenDelete } from '@/api/trigger-token'
export default {
components: {},
props: {},
computed: {
viewOperationLogPagination() {
return COMPUTED_PAGINATION(this.viewOperationLogListQuery)
}
},
data() {
return {
viewOperationLoading: false,
viewOperationLogList: [],
viewOperationLogListQuery: Object.assign(
{ sshId: this.sshId, machineSshId: this.machineSshId },
PAGE_DEFAULT_LIST_QUERY
),
viewOperationLogColumns: [
{
title: '创建人',
dataIndex: 'userId',
width: 100
},
{
title: 'token',
dataIndex: 'triggerToken',
width: 100
},
{
title: '关联数据名',
dataIndex: 'dataName'
// width: 100
},
{
title: '调用次数',
dataIndex: 'triggerCount',
width: 100,
sorter: true
},
{
title: '关联数据',
dataIndex: 'dataId',
width: 100
},
{
title: '创建时间',
dataIndex: 'createTimeMillis',
sorter: true,
customRender: ({ text }) => {
return parseTime(text)
},
width: '180px'
},
{
title: '操作',
dataIndex: 'operation',
width: '80px',
align: 'center',
fixed: 'right'
}
],
allTypeList: []
}
},
created() {
triggerTokenAllType().then((res) => {
if (res.code === 200) {
this.allTypeList = res.data || []
}
})
this.handleListLog()
},
methods: {
handleListLog() {
this.viewOperationLoading = true
triggerTokenList(this.viewOperationLogListQuery).then((res) => {
if (res.code === 200) {
this.viewOperationLogList = res.data.result
this.viewOperationLogListQuery.total = res.data.total
}
this.viewOperationLoading = false
})
},
changeListLog(pagination, filters, sorter) {
this.viewOperationLogListQuery = CHANGE_PAGE(this.viewOperationLogListQuery, { pagination, sorter })
this.handleListLog()
},
//
onchangeListLogTime(value, dateString) {
if (dateString[0]) {
this.viewOperationLogListQuery.createTimeMillis = `${dateString[0]} ~ ${dateString[1]}`
} else {
this.viewOperationLogListQuery.createTimeMillis = ''
}
},
//
handleDelete(record) {
const that = this
$confirm({
title: '系统提示',
zIndex: 1009,
content: '真的要删除对应的触发器吗?',
okText: '确认',
cancelText: '取消',
async onOk() {
return await new Promise((resolve, reject) => {
//
triggerTokenDelete({
id: record.id
})
.then((res) => {
if (res.code === 200) {
$notification.success({
message: res.msg
})
that.handleListLog()
}
resolve()
})
.catch(reject)
})
}
})
}
}
}
</script>