fix script sync

This commit is contained in:
bwcx_jzy 2022-01-20 11:56:13 +08:00
parent 4e6be447ba
commit 53369731c4
No known key found for this signature in database
GPG Key ID: 5E48E9372088B9E5
14 changed files with 287 additions and 18 deletions

View File

@ -103,6 +103,7 @@ public class ScriptController extends BaseAgentController {
//
nodeScriptModel.setContext(HtmlUtil.unescape(nodeScriptModel.getContext()));
NodeScriptModel eModel = nodeScriptServer.getItem(nodeScriptModel.getId());
boolean needCreate = false;
if ("add".equalsIgnoreCase(type)) {
Assert.isNull(eModel, "id已经存在啦");
@ -113,6 +114,13 @@ public class ScriptController extends BaseAgentController {
}
nodeScriptServer.addItem(nodeScriptModel);
return JsonMessage.getString(200, "添加成功");
} else if ("sync".equalsIgnoreCase(type)) {
if (eModel == null) {
eModel = new NodeScriptModel();
eModel.setId(nodeScriptModel.getId());
needCreate = true;
}
eModel.setScriptType("server-sync");
}
Assert.notNull(eModel, "对应数据不存在");
eModel.setName(nodeScriptModel.getName());
@ -120,7 +128,11 @@ public class ScriptController extends BaseAgentController {
eModel.setDescription(nodeScriptModel.getDescription());
eModel.setContext(nodeScriptModel.getContext());
eModel.setDefArgs(nodeScriptModel.getDefArgs());
nodeScriptServer.updateItem(eModel);
if (needCreate) {
nodeScriptServer.addItem(eModel);
} else {
nodeScriptServer.updateItem(eModel);
}
return JsonMessage.getString(200, "修改成功");
}

View File

@ -58,6 +58,18 @@ public class NodeScriptModel extends BaseWorkspaceModel {
* 描述
*/
private String description;
/**
* 脚本类型
*/
private String scriptType;
public String getScriptType() {
return scriptType;
}
public void setScriptType(String scriptType) {
this.scriptType = scriptType;
}
public String getDescription() {
return description;

View File

@ -22,18 +22,24 @@
*/
package io.jpom.controller.script;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.cron.pattern.CronPattern;
import cn.jiangzeyin.common.JsonMessage;
import cn.jiangzeyin.common.validator.ValidatorItem;
import com.alibaba.fastjson.JSONObject;
import io.jpom.common.BaseServerController;
import io.jpom.common.forward.NodeForward;
import io.jpom.common.forward.NodeUrl;
import io.jpom.common.interceptor.PermissionInterceptor;
import io.jpom.model.PageResultDto;
import io.jpom.model.data.NodeModel;
import io.jpom.model.data.UserModel;
import io.jpom.model.script.ScriptModel;
import io.jpom.plugin.ClassFeature;
import io.jpom.plugin.Feature;
import io.jpom.service.node.script.NodeScriptServer;
import io.jpom.service.script.ScriptServer;
import org.springframework.http.MediaType;
import org.springframework.util.Assert;
@ -41,7 +47,10 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.util.Collection;
import java.util.List;
/**
* @author bwcx_jzy
@ -53,9 +62,12 @@ import java.io.File;
public class ScriptController extends BaseServerController {
private final ScriptServer scriptServer;
private final NodeScriptServer nodeScriptServer;
public ScriptController(ScriptServer scriptServer) {
public ScriptController(ScriptServer scriptServer,
NodeScriptServer nodeScriptServer) {
this.scriptServer = scriptServer;
this.nodeScriptServer = nodeScriptServer;
}
/**
@ -75,11 +87,12 @@ public class ScriptController extends BaseServerController {
@ValidatorItem String name,
String autoExecCron,
String defArgs,
String description) {
String description, String nodeIds) {
ScriptModel scriptModel = new ScriptModel();
scriptModel.setId(id);
scriptModel.setContext(context);
scriptModel.setName(name);
scriptModel.setNodeIds(nodeIds);
scriptModel.setDescription(description);
scriptModel.setDefArgs(defArgs);
@ -98,14 +111,52 @@ public class ScriptController extends BaseServerController {
scriptModel.setAutoExecCron(StrUtil.EMPTY);
}
//
String oldNodeIds = null;
if (StrUtil.isEmpty(id)) {
scriptServer.insert(scriptModel);
return JsonMessage.getString(200, "添加成功");
} else {
HttpServletRequest request = getRequest();
ScriptModel byKey = scriptServer.getByKey(id, request);
Assert.notNull(byKey, "没有对应的数据");
oldNodeIds = byKey.getNodeIds();
scriptServer.updateById(scriptModel, request);
}
scriptServer.updateById(scriptModel, getRequest());
this.syncNodeScript(scriptModel, oldNodeIds);
return JsonMessage.getString(200, "修改成功");
}
private void syncNodeScript(ScriptModel scriptModel, String oldNode) {
List<String> oldNodeIds = StrUtil.split(oldNode, StrUtil.COMMA);
List<String> newNodeIds = StrUtil.split(scriptModel.getNodeIds(), StrUtil.COMMA);
Collection<String> delNode = CollUtil.subtract(oldNodeIds, newNodeIds);
UserModel user = getUser();
// 删除
for (String s : delNode) {
NodeModel byKey = nodeService.getByKey(s, getRequest());
JSONObject jsonObject = new JSONObject();
jsonObject.put("id", scriptModel.getId());
JsonMessage<String> request = NodeForward.request(byKey, NodeUrl.Script_Del, user, jsonObject);
Assert.state(request.getCode() == 200, "处理 " + byKey.getName() + " 节点删除脚本失败" + request.getMsg());
nodeScriptServer.syncNode(byKey);
}
// 更新
for (String newNodeId : newNodeIds) {
NodeModel byKey = nodeService.getByKey(newNodeId, getRequest());
JSONObject jsonObject = new JSONObject();
jsonObject.put("id", scriptModel.getId());
jsonObject.put("type", "sync");
jsonObject.put("context", scriptModel.getContext());
jsonObject.put("autoExecCron", scriptModel.getAutoExecCron());
jsonObject.put("defArgs", scriptModel.getDefArgs());
jsonObject.put("description", scriptModel.getDescription());
jsonObject.put("name", scriptModel.getName());
jsonObject.put("workspaceId", scriptModel.getWorkspaceId());
JsonMessage<String> request = NodeForward.request(byKey, NodeUrl.Script_Save, user, jsonObject);
Assert.state(request.getCode() == 200, "处理 " + byKey.getName() + " 节点同步脚本失败" + request.getMsg());
nodeScriptServer.syncNode(byKey);
}
}
@RequestMapping(value = "del.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
public String del(String id) {
ScriptModel server = scriptServer.getByKey(id);

View File

@ -57,6 +57,18 @@ public class ScriptCacheModel extends BaseNodeModel {
* 描述
*/
private String description;
/**
* 脚本类型
*/
private String scriptType;
public String getScriptType() {
return scriptType;
}
public void setScriptType(String scriptType) {
this.scriptType = scriptType;
}
public String getDescription() {
return description;

View File

@ -60,6 +60,18 @@ public class ScriptModel extends BaseWorkspaceModel {
private String description;
private String context;
/**
* 节点ID
*/
private String nodeIds;
public String getNodeIds() {
return nodeIds;
}
public void setNodeIds(String nodeIds) {
this.nodeIds = nodeIds;
}
public String getContext() {
return context;

View File

@ -23,6 +23,7 @@
package io.jpom.service.script;
import io.jpom.model.script.ScriptExecuteLogModel;
import io.jpom.model.script.ScriptModel;
import io.jpom.service.h2db.BaseWorkspaceService;
import org.springframework.stereotype.Service;
@ -32,4 +33,21 @@ import org.springframework.stereotype.Service;
*/
@Service
public class ScriptExecuteLogServer extends BaseWorkspaceService<ScriptExecuteLogModel> {
/**
* 创建执行记录
*
* @param scriptModel 脚本
* @param type 执行类型
* @return 对象
*/
public ScriptExecuteLogModel create(ScriptModel scriptModel, int type) {
ScriptExecuteLogModel scriptExecuteLogModel = new ScriptExecuteLogModel();
scriptExecuteLogModel.setScriptId(scriptModel.getId());
scriptExecuteLogModel.setScriptName(scriptModel.getName());
scriptExecuteLogModel.setTriggerExecType(type);
scriptExecuteLogModel.setWorkspaceId(scriptModel.getWorkspaceId());
super.insert(scriptExecuteLogModel);
return scriptExecuteLogModel;
}
}

View File

@ -22,14 +22,120 @@
*/
package io.jpom.service.script;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.cron.task.Task;
import cn.jiangzeyin.common.DefaultSystemLog;
import cn.jiangzeyin.common.spring.SpringUtil;
import io.jpom.common.BaseServerController;
import io.jpom.cron.CronUtils;
import io.jpom.cron.ICron;
import io.jpom.model.script.ScriptExecuteLogModel;
import io.jpom.model.script.ScriptModel;
import io.jpom.service.h2db.BaseWorkspaceService;
import io.jpom.socket.ScriptProcessBuilder;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* @author bwcx_jzy
* @since 2022/1/19
*/
@Service
public class ScriptServer extends BaseWorkspaceService<ScriptModel> {
public class ScriptServer extends BaseWorkspaceService<ScriptModel> implements ICron {
@Override
public int startCron() {
String sql = "select * from " + super.getTableName() + " where autoExecCron is not null and autoExecCron <> ''";
List<ScriptModel> models = super.queryList(sql);
if (models == null) {
return 0;
}
for (ScriptModel item : models) {
this.checkCron(item);
}
return CollUtil.size(models);
}
@Override
public void insert(ScriptModel scriptModel) {
super.insert(scriptModel);
this.checkCron(scriptModel);
}
@Override
public int updateById(ScriptModel info) {
int i = super.updateById(info);
if (i > 0) {
this.checkCron(info);
}
return i;
}
@Override
public int update(ScriptModel scriptModel) {
int update = super.update(scriptModel);
if (update > 0) {
this.checkCron(scriptModel);
}
return update;
}
@Override
public int delByKey(String keyValue) {
int delByKey = super.delByKey(keyValue);
if (delByKey > 0) {
String taskId = "server_script:" + keyValue;
CronUtils.remove(taskId);
}
return delByKey;
}
/**
* 检查定时任务 状态
*
* @param scriptModel 构建信息
*/
private void checkCron(ScriptModel scriptModel) {
String id = scriptModel.getId();
String taskId = "server_script:" + id;
String autoExecCron = scriptModel.getAutoExecCron();
if (StrUtil.isEmpty(autoExecCron)) {
CronUtils.remove(taskId);
return;
}
DefaultSystemLog.getLog().debug("start script cron {} {} {}", id, scriptModel.getName(), autoExecCron);
CronUtils.upsert(taskId, autoExecCron, new CronTask(id));
}
private static class CronTask implements Task {
private final String id;
public CronTask(String id) {
this.id = id;
}
@Override
public void execute() {
try {
ScriptServer nodeScriptServer = SpringUtil.getBean(ScriptServer.class);
ScriptModel scriptServerItem = nodeScriptServer.getByKey(id);
if (scriptServerItem == null) {
return;
}
// 创建记录
ScriptExecuteLogServer execLogServer = SpringUtil.getBean(ScriptExecuteLogServer.class);
ScriptExecuteLogModel nodeScriptExecLogModel = execLogServer.create(scriptServerItem, 1);
// 执行
ScriptProcessBuilder.create(scriptServerItem, nodeScriptExecLogModel.getId(), scriptServerItem.getDefArgs());
} catch (Exception e) {
DefaultSystemLog.getLog().error("触发自动执行命令模版异常", e);
} finally {
BaseServerController.removeEmpty();
}
}
}
}

View File

@ -127,13 +127,7 @@ public class ServerScriptHandler extends BaseProxyHandler {
scriptCacheModel.setLastRunUser(userModel.getId());
nodeScriptServer.update(scriptCacheModel);
//
ScriptExecuteLogModel scriptExecuteLogCacheModel = new ScriptExecuteLogModel();
scriptExecuteLogCacheModel.setScriptId(scriptModel.getId());
scriptExecuteLogCacheModel.setScriptName(scriptModel.getName());
scriptExecuteLogCacheModel.setTriggerExecType(0);
scriptExecuteLogCacheModel.setWorkspaceId(scriptModel.getWorkspaceId());
logServer.insert(scriptExecuteLogCacheModel);
ScriptExecuteLogModel scriptExecuteLogCacheModel = logServer.create(scriptCacheModel, 0);
return scriptExecuteLogCacheModel.getId();
} finally {
BaseServerController.removeAll();

View File

@ -36,3 +36,9 @@ ALTER TABLE NODE_INFO
ALTER TABLE SCRIPT_INFO
ADD IF NOT EXISTS description varchar(255) comment '描述';
ALTER TABLE SCRIPT_INFO
ADD IF NOT EXISTS scriptType varchar(100) comment '脚本类型';
ALTER TABLE SERVER_SCRIPT_INFO
ADD IF NOT EXISTS nodeIds CLOB comment '绑定的节点 id';

View File

@ -261,7 +261,7 @@
DSL 内容
<a-tooltip v-show="temp.type !== 'edit'">
<template slot="title">
<p> yaml/yml 格式配置,scriptId 脚本模版ID可以到脚本模版编辑弹窗中查看 scriptId</p>
<p> yaml/yml 格式配置,scriptId 服务端脚本模版ID可以到服务端脚本模版编辑弹窗中查看 scriptId</p>
<p>脚本里面支持的变量有#{PROJECT_ID}#{PROJECT_NAME}#{PROJECT_PATH}</p>
<p><b>status</b> 流程执行完脚本后输出的内容最后一行必须为running:$pid <b>$pid 为当前项目实际的进程ID</b>如果输出最后一行不是预期格式项目状态将是未运行</p>
<p>配置示例</p>

View File

@ -31,7 +31,7 @@
<template slot="operation" slot-scope="text, record">
<a-space>
<a-button type="primary" @click="handleExec(record)">执行</a-button>
<a-button type="primary" @click="handleEdit(record)">编辑</a-button>
<a-button :type="`${record.scriptType === 'server-sync' ? '' : 'primary'}`" @click="handleEdit(record)">{{ record.scriptType === "server-sync" ? "查看" : " 编辑" }}</a-button>
<a-button type="danger" @click="handleDelete(record)">删除</a-button>
</a-space>
</template>
@ -39,6 +39,7 @@
<!-- 编辑区 -->
<a-modal v-model="editScriptVisible" title="编辑 Script" @ok="handleEditScriptOk" :maskClosable="false" width="80vw">
<a-form-model ref="editScriptForm" :rules="rules" :model="temp" :label-col="{ span: 3 }" :wrapper-col="{ span: 18 }">
<a-alert v-if="this.temp.scriptType === 'server-sync'" message="服务端同步的脚本不能在此修改" banner />
<a-form-model-item v-if="temp.id" label="ScriptId" prop="id">
<a-input v-model="temp.id" disabled readonly />
</a-form-model-item>
@ -119,6 +120,7 @@ export default {
columns: [
// { title: "Script ID", dataIndex: "id", width: 200, ellipsis: true, scopedSlots: { customRender: "id" } },
{ title: "名称", dataIndex: "name", ellipsis: true, scopedSlots: { customRender: "name" } },
{ title: "定时执行", dataIndex: "autoExecCron", ellipsis: true, scopedSlots: { customRender: "autoExecCron" } },
{ title: "修改时间", dataIndex: "modifyTimeMillis", width: 180, ellipsis: true, scopedSlots: { customRender: "modifyTimeMillis" } },
{ title: "修改人", dataIndex: "modifyUser", ellipsis: true, scopedSlots: { customRender: "modifyUser" }, width: 120 },
{ title: "最后操作人", dataIndex: "lastRunUser", ellipsis: true, scopedSlots: { customRender: "lastRunUser" } },
@ -186,6 +188,12 @@ export default {
},
// Script
handleEditScriptOk() {
if (this.temp.scriptType === "server-sync") {
this.$notification.warning({
message: "服务端同步的脚本不能在此修改",
});
return;
}
//
this.$refs["editScriptForm"].validate((valid) => {
if (!valid) {

View File

@ -51,7 +51,7 @@
<template slot="operation" slot-scope="text, record">
<a-space>
<a-button type="primary" @click="handleExec(record)">执行</a-button>
<a-button type="primary" @click="handleEdit(record)">编辑</a-button>
<a-button :type="`${record.scriptType === 'server-sync' ? '' : 'primary'}`" @click="handleEdit(record)">{{ record.scriptType === "server-sync" ? "查看" : " 编辑" }}</a-button>
<a-button type="danger" @click="handleDelete(record)">删除</a-button>
</a-space>
</template>
@ -59,6 +59,7 @@
<!-- 编辑区 -->
<a-modal v-model="editScriptVisible" title="编辑 Script" @ok="handleEditScriptOk" :maskClosable="false" width="80vw">
<a-form-model ref="editScriptForm" :rules="rules" :model="temp" :label-col="{ span: 3 }" :wrapper-col="{ span: 19 }">
<a-alert v-if="this.temp.scriptType === 'server-sync'" message="服务端同步的脚本不能在此修改" banner />
<a-form-model-item label="Script 名称" prop="name">
<a-input v-model="temp.name" placeholder="名称" />
</a-form-model-item>
@ -121,6 +122,7 @@ export default {
// { title: "Script ID", dataIndex: "id", width: 200, ellipsis: true, scopedSlots: { customRender: "id" } },
{ title: "名称", dataIndex: "name", ellipsis: true, scopedSlots: { customRender: "name" } },
{ title: "节点名称", dataIndex: "nodeId", ellipsis: true, scopedSlots: { customRender: "nodeId" } },
{ title: "定时执行", dataIndex: "autoExecCron", ellipsis: true, scopedSlots: { customRender: "autoExecCron" } },
{ title: "修改时间", dataIndex: "modifyTimeMillis", width: 170, ellipsis: true, scopedSlots: { customRender: "modifyTimeMillis" } },
{ title: "修改人", dataIndex: "modifyUser", ellipsis: true, scopedSlots: { customRender: "modifyUser" }, width: 120 },
{ title: "最后操作人", dataIndex: "lastRunUser", ellipsis: true, scopedSlots: { customRender: "lastRunUser" } },
@ -188,6 +190,12 @@ export default {
},
// Script
handleEditScriptOk() {
if (this.temp.scriptType === "server-sync") {
this.$notification.warning({
message: "服务端同步的脚本不能在此修改",
});
return;
}
//
this.$refs["editScriptForm"].validate((valid) => {
if (!valid) {

View File

@ -16,6 +16,7 @@
<ul>
<li>执行时候默认不加载全部环境变量需要脚本里面自行加载</li>
<li>命令文件将在 ${数据目录}/script/xxxx.shbat 执行</li>
<li>分发节点是指在编辑完脚本后自动将脚本内容同步节点的脚本,一般用户节点分发功能中的 DSL 模式</li>
</ul>
</div>
</template>
@ -52,6 +53,9 @@
<!-- 编辑区 -->
<a-modal v-model="editScriptVisible" title="编辑 Script" @ok="handleEditScriptOk" :maskClosable="false" width="80vw">
<a-form-model ref="editScriptForm" :rules="rules" :model="temp" :label-col="{ span: 3 }" :wrapper-col="{ span: 19 }">
<a-form-model-item v-if="temp.id" label="ScriptId" prop="id">
<a-input v-model="temp.id" disabled readonly />
</a-form-model-item>
<a-form-model-item label="Script 名称" prop="name">
<a-input v-model="temp.name" placeholder="名称" />
</a-form-model-item>
@ -78,6 +82,20 @@
<a-form-model-item label="描述" prop="description">
<a-input v-model="temp.description" type="textarea" :rows="3" style="resize: none" placeholder="详细描述" />
</a-form-model-item>
<a-form-model-item>
<template slot="label">
分发节点
<a-tooltip v-show="!temp.id">
<template slot="title"> 分发节点是指在编辑完脚本后自动将脚本内容同步节点的脚本中 </template>
<a-icon type="question-circle" theme="filled" />
</a-tooltip>
</template>
<a-select show-search option-filter-prop="children" placeholder="请选择分发到的节点" mode="multiple" v-model="temp.chooseNode">
<a-select-option v-for="item in nodeList" :key="item.id" :value="item.id">
{{ item.name }}
</a-select-option>
</a-select>
</a-form-model-item>
</a-form-model>
</a-modal>
<!-- 脚本控制台组件 -->
@ -89,6 +107,7 @@
<script>
import { getScriptListAll, editScript, deleteScript } from "@/api/server-script";
import codeEditor from "@/components/codeEditor";
import { getNodeListAll } from "@/api/node";
import ScriptConsole from "@/pages/script/script-console";
import { PAGE_DEFAULT_LIMIT, PAGE_DEFAULT_SIZW_OPTIONS, PAGE_DEFAULT_SHOW_TOTAL, PAGE_DEFAULT_LIST_QUERY, CRON_DATA_SOURCE } from "@/utils/const";
import { parseTime } from "@/utils/time";
@ -105,7 +124,7 @@ export default {
cronDataSource: CRON_DATA_SOURCE,
list: [],
temp: {},
nodeList: [],
editScriptVisible: false,
drawerTitle: "",
drawerConsoleVisible: false,
@ -159,15 +178,25 @@ export default {
parseTime(v) {
return parseTime(v);
},
//
getAllNodeList() {
getNodeListAll().then((res) => {
this.nodeList = res.data || [];
});
},
createScript() {
this.temp = {};
this.editScriptVisible = true;
this.getAllNodeList();
},
//
handleEdit(record) {
this.temp = record;
//
// this.temp.;
this.temp = { ...this.temp, chooseNode: record.nodeIds ? record.nodeIds.split(",") : [] };
this.editScriptVisible = true;
this.getAllNodeList();
},
// Script
handleEditScriptOk() {
@ -177,6 +206,7 @@ export default {
return false;
}
//
this.temp.nodeIds = this.temp?.chooseNode?.join(",");
editScript(this.temp).then((res) => {
if (res.code === 200) {
//

View File

@ -53,7 +53,7 @@
</div>
</a-form-model-item>
<a-form-model-item label="SSH节点">
<a-select show-search option-filter-prop="children" mode="multiple" v-model="chooseSsh">
<a-select show-search option-filter-prop="children" placeholder="请选择SSH节点" mode="multiple" v-model="chooseSsh">
<a-select-option v-for="item in sshList" :key="item.id" :value="item.id">
{{ item.name }}
</a-select-option>