Merge pull request !105 from Arno/dev
This commit is contained in:
不忘初心 2019-04-26 13:38:54 +08:00 committed by Arno
commit c7914d66e8
64 changed files with 1799 additions and 296 deletions

View File

@ -0,0 +1,5 @@
### 使用的JDK版本、Jpom版本、操作系统及系统版本
### 问题描述(包括截图)
### 报错信息

View File

@ -4,9 +4,12 @@
### 新增功能
1. 新增线程列表监控(感谢@其锋)
2. 新增节点脚本模板(感谢@其锋)
### 解决BUG、优化功能
1. 【Server】节点首页右上角管理路径错误(感谢@其锋)
1. 【Server】节点首页右上角管理路径错误(感谢@其锋)
-----------------------------------------------------------

2
FQA.md
View File

@ -82,6 +82,8 @@
https://github.com/alibaba/arthas/issues/347
https://bugs.java.com/bugdatabase/view_bug.do?bug_id=4770092
### windows 环境项目在运行中不能删除文件
> 由于系统原因,暂时还没有找到解决办法

View File

@ -7,4 +7,4 @@
### 待优化解决
1. 支持更多压缩包
2. 项目监控完善
2. war包管理

View File

@ -147,13 +147,15 @@ Agent.sh status 查看Jpom插件端运行状态
### 常见问题、操作说明
| 说明1 | 说明2 |
| 必看 | 选看 |
| -- | -- |
| [安装文档>>](/doc/install.md) | [用户角色说明>>](/doc/userRole.md) |
| [常见问题>>](/FQA.md) | [阿里云Oss配置>>](/doc/CodePipeline-Oss.md) |
| [启动失败问题>>](/doc/startFail.md) | [更新日志>>](/CHANGELOG.md) |
| | [开发计划>>](/PLANS.md) |
| [项目属性说明>>](/doc/project.md) | [删除项目说明>>](/doc/deleteProject.md) |
| [安装文档>>](/doc/install.md) | [常见问题>>](/FQA.md) |
| [启动失败问题>>](/doc/startFail.md) | [阿里云Oss配置>>](/doc/CodePipeline-Oss.md)|
| [项目属性说明>>](/doc/project.md) | [删除项目说明>>](/doc/deleteProject.md) |
| [白名单规则>>](/doc/whitelist.md) | [Nginx管理规则>>](/doc/nginx-manager.md) |
| [用户角色说明>>](/doc/userRole.md) | [推荐Nginx配置>>](/doc/nginx-config.md) |
| [更新日志>>](/CHANGELOG.md) | [开发计划>>](/PLANS.md) |
### 交流讨论 、提供bug反馈或建议

View File

@ -27,6 +27,8 @@
3. 默认密码为随机生成的10个字符串
4. 自动生成的授权账号要写入文件中方便后期查看
5. 文件具体位置请注意控制台日志
【注意请牢记插件端的授权账号如果拥有授权端账号信息将可以越过Server用户权限操作节点数据】
7. 如果顺利启动那么Jpom的插件端agent算安装成功
-----------------------------------------------------------------------------------

14
doc/nginx-manager.md Normal file
View File

@ -0,0 +1,14 @@
## Jpom 中对nginx 管理
> 为了方便运维日志简单的对项目上线Jpom可以对nginx的配置进行管理
#### 管理规则如下
1. 系统根据配置的nginx白名单去扫描路径的*.conf 文件展示
2. 提供给用户进行在线编辑、保存
3. 保存后系统自动执行`nginx -s reload`
4. 只有节点管理员可以编辑保存nginx配置文件
#### 如果需要配置ssl 请配合Jpom 中的证书管理一起使用
## 为了系统安全只用系统管理员可以配置nginx的静态资源路径【root、alias】

62
doc/whitelist.md Normal file
View File

@ -0,0 +1,62 @@
# Jpom 中的白名单说明
> Jpom 中的白名单的由来,由于项目管理都需要对项目的文件进行管理。在创建项目的时候需要确定项目的相关文件存放的路径。
> 那么此时由用户决定存放到哪里,现在有点冒然。因为服务器中有些路径已经存放重要配置文件,此时项目路径相同那么必然没有任何安全性
##### 假设设置黑名单
> 如果设置黑名单那么没有办法最快速收集用户不同服务器中重要文件路径,此方法也显得不合适
### 综上Jpom 就使用白名单来管理项目相关的文件
> 那么在Jpom 中有那些地方需要用到白名单呢
## 1. 项目路径
> 项目路径白名单主要是决定不同项目存放的位置,【但是项目路径选择的白名单不是项目文件存放的实际位置】
> 项目文件存放是实际路径是由【选择的项目路径白名单+项目Jar包】 组合而成的
```
# 举例说明项目白名单如何使用
如果有4个项目需要部署到服务器中但是4个项目又可以分为两大类型
项目1、项目2、项目3、项目4
可以分为:后台、接口
【后台】项目1、项目3
【接口】项目2、项目4
那么推荐配置白名单:
/project/admin/
/project/api/
那么在创建项目1、项目3的时候选择路径/project/admin/
那么在创建项目2、项目4的时候选择路径/project/api/
```
## 2. 证书路径
> 证书路径白名单是决定用户上次的ssl 证书存放的路径
> 证书文件实际存放的路径是由【选择的证书路径白名单+证书id+id.key(id.pem)】 组合而成的
## 3. Nginx路径
> Nginx路径白名单是决定Jpom 程序会自动扫描对应目录下的 *.conf 文件还展示配置文件
## 4. 节点分发
> 节点分发白名单是决定创建节点分发项目时候,项目的白名单路径(此处规则和项目路径白名单一致)
> 单独管理节点分发白名单的目的是为了多节点的白名单信息同步
# 注意:为了系统安全白名单只允许系统管理配置,在节点第一次使用时候为了系统能正常使用需要添加一个项目的白名单路径

View File

@ -3,6 +3,7 @@ package cn.keepbx.jpom.common;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.servlet.ServletUtil;
import cn.jiangzeyin.controller.base.AbstractController;
import cn.keepbx.jpom.model.Role;
import cn.keepbx.jpom.model.data.ProjectInfoModel;
import cn.keepbx.jpom.service.manage.ProjectInfoService;
@ -32,12 +33,25 @@ public abstract class BaseAgentController extends BaseJpomController {
* @param request req
* @return name
*/
public static String getUserName(HttpServletRequest request) {
private static String getUserName(HttpServletRequest request) {
String name = ServletUtil.getHeaderIgnoreCase(request, ConfigBean.JPOM_SERVER_USER_NAME);
name = CharsetUtil.convert(name, CharsetUtil.CHARSET_ISO_8859_1, CharsetUtil.CHARSET_UTF_8);
return StrUtil.emptyToDefault(name, StrUtil.DASHED);
}
/**
* 获取server 端操作人
*
* @return name
*/
public static String getNowUserName() {
HttpServletRequest request = AbstractController.getRequestAttributes().getRequest();
if (request == null) {
return StrUtil.DASHED;
}
return getUserName(request);
}
/**
* 操作的人员是否为系统管理员
*

View File

@ -1,5 +1,6 @@
package cn.keepbx.jpom.controller;
import cn.hutool.system.SystemUtil;
import cn.jiangzeyin.common.JsonMessage;
import cn.keepbx.jpom.BaseJpomApplication;
import cn.keepbx.jpom.common.BaseAgentController;
@ -61,6 +62,8 @@ public class IndexController extends BaseAgentController {
jsonObject.put("javaVirtualCount", JvmUtil.getJavaVirtualCount());
jsonObject.put("osName", BaseJpomApplication.OS_INFO.getName());
jsonObject.put("jpomVersion", JpomManifest.getInstance().getVersion());
jsonObject.put("javaVersion", SystemUtil.getJavaRuntimeInfo().getVersion());
if (projectInfoModels == null) {
jsonObject.put("count", 0);
jsonObject.put("runCount", 0);

View File

@ -9,7 +9,7 @@ import cn.keepbx.jpom.common.BaseAgentController;
import cn.keepbx.jpom.model.data.ProjectInfoModel;
import cn.keepbx.jpom.service.manage.ConsoleService;
import cn.keepbx.jpom.service.oss.OssManagerService;
import cn.keepbx.jpom.socket.CommandOp;
import cn.keepbx.jpom.socket.ConsoleCommandOp;
import com.alibaba.fastjson.JSONArray;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMapping;
@ -79,7 +79,7 @@ public class BuildController extends BaseAgentController {
// 修改使用状态
projectInfoModel.setUseLibDesc("build");
projectInfoService.updateItem(projectInfoModel);
String result = consoleService.execCommand(CommandOp.restart, projectInfoModel);
String result = consoleService.execCommand(ConsoleCommandOp.restart, projectInfoModel);
return JsonMessage.getString(200, "安装成功,已自动重启,当前状态是:" + result);
}
}

View File

@ -9,13 +9,13 @@ import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil;
import cn.jiangzeyin.common.DefaultSystemLog;
import cn.jiangzeyin.common.JsonMessage;
import cn.keepbx.jpom.BaseJpomApplication;
import cn.keepbx.jpom.common.BaseAgentController;
import cn.keepbx.jpom.common.commander.AbstractProjectCommander;
import cn.keepbx.jpom.model.Role;
import cn.keepbx.jpom.model.RunMode;
import cn.keepbx.jpom.model.data.ProjectInfoModel;
import cn.keepbx.jpom.service.WhitelistDirectoryService;
import cn.keepbx.jpom.socket.CommonSocketConfig;
import cn.keepbx.jpom.system.ConfigBean;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMapping;
@ -55,8 +55,8 @@ public class EditProjectController extends BaseAgentController {
if (!Validator.isGeneral(id, 2, 20)) {
return JsonMessage.getString(401, "项目id 长度范围2-20英文字母 、数字和下划线)");
}
if (CommonSocketConfig.SYSTEM_ID.equals(id)) {
return JsonMessage.getString(401, "项目id " + CommonSocketConfig.SYSTEM_ID + " 关键词被系统占用");
if (BaseJpomApplication.SYSTEM_ID.equals(id)) {
return JsonMessage.getString(401, "项目id " + BaseJpomApplication.SYSTEM_ID + " 关键词被系统占用");
}
// 防止和Jpom冲突
if (StrUtil.isNotEmpty(ConfigBean.getInstance().applicationTag) && ConfigBean.getInstance().applicationTag.equalsIgnoreCase(id)) {
@ -288,7 +288,11 @@ public class EditProjectController extends BaseAgentController {
return null;
}
/**
* 删除项目
*
* @return json
*/
@RequestMapping(value = "deleteProject", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public String deleteProject() {
ProjectInfoModel projectInfoModel = tryGetProjectInfoModel();
@ -300,8 +304,7 @@ public class EditProjectController extends BaseAgentController {
if (projectInfoModel.isStatus(true)) {
return JsonMessage.getString(401, "不能删除正在运行的项目");
}
String userId = getUserName();
projectInfoService.deleteProject(projectInfoModel, userId);
projectInfoService.deleteItem(projectInfoModel.getId());
return JsonMessage.getString(200, "删除成功!");
} catch (Exception e) {
DefaultSystemLog.ERROR().error(e.getMessage(), e);
@ -309,6 +312,13 @@ public class EditProjectController extends BaseAgentController {
}
}
/**
* 检查项目lib 情况
*
* @param id 项目id
* @param newLib 新路径
* @return 状态码400是一定不能操作的401 是提醒
*/
@RequestMapping(value = "judge_lib.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public String saveProject(String id, String newLib) {
File file = new File(newLib);

View File

@ -11,7 +11,7 @@ import cn.jiangzeyin.controller.multipart.MultipartFileBuilder;
import cn.keepbx.jpom.common.BaseAgentController;
import cn.keepbx.jpom.model.data.ProjectInfoModel;
import cn.keepbx.jpom.service.manage.ConsoleService;
import cn.keepbx.jpom.socket.CommandOp;
import cn.keepbx.jpom.socket.ConsoleCommandOp;
import cn.keepbx.jpom.system.AgentConfigBean;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
@ -120,7 +120,7 @@ public class ProjectFileControl extends BaseAgentController {
//
String after = getParameter("after");
if ("restart".equalsIgnoreCase(after)) {
String result = consoleService.execCommand(CommandOp.restart, pim);
String result = consoleService.execCommand(ConsoleCommandOp.restart, pim);
return JsonMessage.getString(200, "上传成功并重启:" + result);
}

View File

@ -120,7 +120,7 @@ public class ProjectListController extends BaseAgentController {
* @return obj
*/
@RequestMapping(value = "getProjectPort", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public String getProjectGroup(String ids) {
public String getProjectPort(String ids) {
if (StrUtil.isEmpty(ids)) {
return JsonMessage.getString(400, "");
}
@ -147,15 +147,4 @@ public class ProjectListController extends BaseAgentController {
}
return JsonMessage.getString(200, "", jsonObject);
}
// /**
// * 获取运行方式
// *
// * @return array
// */
// @RequestMapping(value = "getRunModes", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
// public String getRunModes() {
// ProjectInfoModel.RunMode[] runModes = ProjectInfoModel.RunMode.values();
// return JsonMessage.getString(200, "", runModes);
// }
}

View File

@ -0,0 +1,113 @@
package cn.keepbx.jpom.controller.script;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import cn.jiangzeyin.common.JsonMessage;
import cn.jiangzeyin.controller.multipart.MultipartFileBuilder;
import cn.keepbx.jpom.common.BaseAgentController;
import cn.keepbx.jpom.model.data.ScriptModel;
import cn.keepbx.jpom.service.script.ScriptServer;
import cn.keepbx.jpom.system.AgentConfigBean;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.io.File;
import java.io.IOException;
/**
* 脚本管理
*
* @author jiangzeyin
* @date 2019/4/24
*/
@RestController
@RequestMapping(value = "/script")
public class ScriptController extends BaseAgentController {
@Resource
private ScriptServer scriptServer;
@RequestMapping(value = "list.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public String list() throws IOException {
return JsonMessage.getString(200, "", scriptServer.list());
}
@RequestMapping(value = "item.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public String item(String id) throws IOException {
return JsonMessage.getString(200, "", scriptServer.getItem(id));
}
@RequestMapping(value = "save.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public String save(ScriptModel scriptModel, String type) throws IOException {
if (scriptModel == null) {
return JsonMessage.getString(405, "没有数据");
}
boolean safe = checkPathSafe(scriptModel.getId());
if (!safe) {
return JsonMessage.getString(405, "id规则不合法");
}
if (StrUtil.isEmpty(scriptModel.getContext())) {
return JsonMessage.getString(405, "内容为空");
}
ScriptModel eModel = scriptServer.getItem(scriptModel.getId());
if ("add".equalsIgnoreCase(type)) {
if (eModel != null) {
return JsonMessage.getString(405, "id已经存在啦");
}
File file = scriptModel.getFile(true);
if (file.exists() || file.isDirectory()) {
return JsonMessage.getString(405, "当地id路径文件已经存在来请修改");
}
scriptServer.addItem(scriptModel);
return JsonMessage.getString(200, "添加成功");
}
if (eModel == null) {
return JsonMessage.getString(405, "对应数据不存在");
}
eModel.setName(scriptModel.getName());
eModel.setContext(scriptModel.getContext());
boolean b = scriptServer.updateItem(eModel);
if (b) {
return JsonMessage.getString(200, "修改成功");
}
return JsonMessage.getString(500, "修改失败");
}
@RequestMapping(value = "del.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public String del(String id) throws IOException {
scriptServer.deleteItem(id);
return JsonMessage.getString(200, "删除成功");
}
@RequestMapping(value = "upload.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
public String upload() throws IOException {
MultipartFileBuilder multipartFileBuilder = createMultipart()
.addFieldName("file").setFileExt("bat", "sh");
multipartFileBuilder.setSavePath(AgentConfigBean.getInstance().getTempPathName());
multipartFileBuilder.setUseOriginalFilename(true);
String path = multipartFileBuilder.save();
File file = FileUtil.file(path);
String context = FileUtil.readString(path, CharsetUtil.CHARSET_UTF_8);
if (StrUtil.isEmpty(context)) {
return JsonMessage.getString(405, "脚本内容为空");
}
String id = file.getName();
ScriptModel eModel = scriptServer.getItem(id);
if (eModel != null) {
return JsonMessage.getString(405, "对应脚本模板已经存在啦");
}
eModel = new ScriptModel();
eModel.setId(id);
eModel.setName(id);
eModel.setContext(context);
file = eModel.getFile(true);
if (file.exists() || file.isDirectory()) {
return JsonMessage.getString(405, "当地id路径文件已经存在来请修改");
}
scriptServer.addItem(eModel);
return JsonMessage.getString(200, "导入成功");
}
}

View File

@ -145,6 +145,7 @@ public class CertificateController extends BaseAgentController {
String filePathItem = String.format("%s/%s/%s", path, certModel.getId(), keyName);
InputStream inputStream = zipFile.getInputStream(zipEntry);
FileUtil.writeFromStream(inputStream, filePathItem);
certModel.setType(CertModel.Type.pem);
pemPath = filePathItem;
}
// cer 文件
@ -152,6 +153,7 @@ public class CertificateController extends BaseAgentController {
String filePathItem = String.format("%s/%s/%s", path, certModel.getId(), keyName);
InputStream inputStream = zipFile.getInputStream(zipEntry);
FileUtil.writeFromStream(inputStream, filePathItem);
certModel.setType(CertModel.Type.cer);
pemPath = filePathItem;
}
//
@ -204,7 +206,7 @@ public class CertificateController extends BaseAgentController {
}
// 移动位置
String temporary = certModel.getWhitePath() + "/" + certModel.getId() + "/";
File pemFile = FileUtil.file(temporary + certModel.getId() + ".pem");
File pemFile = FileUtil.file(temporary + certModel.getId() + "." + certModel.getType().name());
File keyFile = FileUtil.file(temporary + certModel.getId() + ".key");
if (add) {
if (pemFile.exists()) {
@ -256,10 +258,7 @@ public class CertificateController extends BaseAgentController {
if (!isSystemUser()) {
return JsonMessage.getString(400, "你没有操作权限");
}
boolean b = certService.delete(id);
if (!b) {
return JsonMessage.getString(400, "删除失败");
}
certService.deleteItem(id);
return JsonMessage.getString(200, "删除成功");
}

View File

@ -53,7 +53,15 @@ public class CertModel extends BaseModel {
* 白名单路径
*/
private String whitePath;
private Type type;
public Type getType() {
return type;
}
public void setType(Type type) {
this.type = type;
}
public String getWhitePath() {
return whitePath;
@ -181,4 +189,12 @@ public class CertModel extends BaseModel {
}
return null;
}
public enum Type {
/**
*
*/
pem,
cer
}
}

View File

@ -89,13 +89,10 @@ public class ProjectInfoModel extends BaseModel {
/**
* 项目是否正在运行
*
* @param get 防止并发获取
* @param get 防止自动获取
* @return true 正在运行
*/
public boolean isStatus(boolean get) {
if (!get) {
return false;
}
try {
status = AbstractProjectCommander.getInstance().isRun(getId());
} catch (Exception e) {

View File

@ -0,0 +1,123 @@
package cn.keepbx.jpom.model.data;
import cn.hutool.core.date.DatePattern;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import cn.keepbx.jpom.BaseJpomApplication;
import cn.keepbx.jpom.model.BaseModel;
import cn.keepbx.jpom.system.AgentConfigBean;
import cn.keepbx.jpom.util.CommandUtil;
import java.io.File;
/**
* 脚本模板
*
* @author jiangzeyin
* @date 2019/4/24
*/
public class ScriptModel extends BaseModel {
/**
* 文件后缀
*/
private static final String SUFFIX;
static {
if (BaseJpomApplication.OS_INFO.isWindows()) {
SUFFIX = "bat";
} else {
SUFFIX = "sh";
}
}
/**
* 最后执行人员
*/
private String lastRunUser;
/**
* 最后修改时间
*/
private String modifyTime;
/**
* 脚本内容
*/
private String context;
public String getLastRunUser() {
return StrUtil.emptyToDefault(lastRunUser, StrUtil.DASHED);
}
public void setLastRunUser(String lastRunUser) {
this.lastRunUser = lastRunUser;
}
public String getModifyTime() {
return modifyTime;
}
public void setModifyTime(String modifyTime) {
this.modifyTime = modifyTime;
}
public String getContext() {
return context;
}
public void setContext(String context) {
this.context = context;
}
public File getFile(boolean get) {
if (StrUtil.isEmpty(getId())) {
throw new IllegalArgumentException("id 为空");
}
File path = AgentConfigBean.getInstance().getScriptPath();
return FileUtil.file(path, getId(), "script." + SUFFIX);
}
public File getLogFile(boolean get) {
if (StrUtil.isEmpty(getId())) {
throw new IllegalArgumentException("id 为空");
}
File path = AgentConfigBean.getInstance().getScriptPath();
File logFile;
int count = 0;
do {
String now = DateTime.now().toString(DatePattern.PURE_DATETIME_PATTERN);
logFile = FileUtil.file(path, getId(), "log", now + count + ".log");
count++;
} while (FileUtil.exist(logFile));
return logFile;
}
public void saveFile() {
File file = getFile(true);
FileUtil.writeString(getContext(), file, CharsetUtil.CHARSET_UTF_8);
// 添加权限
if (BaseJpomApplication.OS_INFO.isLinux()) {
CommandUtil.execCommand("chmod 755 " + FileUtil.getAbsolutePath(file));
}
}
/**
* 读取文件信息
*/
public void readFileTime() {
File file = getFile(true);
long lastModified = file.lastModified();
setModifyTime(DateUtil.date(lastModified).toString());
}
public void readFileContext() {
File file = getFile(true);
if (FileUtil.exist(file)) {
//
String context = FileUtil.readString(file, CharsetUtil.CHARSET_UTF_8);
setContext(context);
}
}
}

View File

@ -2,7 +2,7 @@ package cn.keepbx.jpom.service.manage;
import cn.keepbx.jpom.common.commander.AbstractProjectCommander;
import cn.keepbx.jpom.model.data.ProjectInfoModel;
import cn.keepbx.jpom.socket.CommandOp;
import cn.keepbx.jpom.socket.ConsoleCommandOp;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
@ -21,14 +21,14 @@ public class ConsoleService {
/**
* 执行shell命令
*
* @param commandOp 执行的操作
* @param consoleCommandOp 执行的操作
* @param projectInfoModel 项目信息
*/
public String execCommand(CommandOp commandOp, ProjectInfoModel projectInfoModel) throws Exception {
public String execCommand(ConsoleCommandOp consoleCommandOp, ProjectInfoModel projectInfoModel) throws Exception {
String result;
AbstractProjectCommander abstractProjectCommander = AbstractProjectCommander.getInstance();
// 执行命令
switch (commandOp) {
switch (consoleCommandOp) {
case restart:
result = abstractProjectCommander.restart(projectInfoModel);
break;
@ -46,10 +46,10 @@ public class ConsoleService {
case top:
case showlog:
default:
throw new IllegalArgumentException(commandOp + " error");
throw new IllegalArgumentException(consoleCommandOp + " error");
}
// 通知日志刷新
if (commandOp == CommandOp.start || commandOp == CommandOp.restart) {
if (consoleCommandOp == ConsoleCommandOp.start || consoleCommandOp == ConsoleCommandOp.restart) {
// 修改 run lib 使用情况
ProjectInfoModel modify = projectInfoService.getItem(projectInfoModel.getId());
modify.setRunLibDesc(projectInfoModel.getUseLibDesc());

View File

@ -2,6 +2,7 @@ package cn.keepbx.jpom.service.manage;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.keepbx.jpom.common.BaseAgentController;
import cn.keepbx.jpom.common.BaseOperService;
import cn.keepbx.jpom.model.data.ProjectInfoModel;
import cn.keepbx.jpom.model.data.ProjectRecoverModel;
@ -62,9 +63,12 @@ public class ProjectInfoService extends BaseOperService<ProjectInfoModel> {
/**
* 删除项目
*
* @param projectInfo 项目
* @param id 项目
*/
public void deleteProject(ProjectInfoModel projectInfo, String userId) throws Exception {
@Override
public void deleteItem(String id) {
ProjectInfoModel projectInfo = getItem(id);
String userId = BaseAgentController.getNowUserName();
deleteJson(AgentConfigBean.PROJECT, projectInfo.getId());
// 添加回收记录
ProjectRecoverModel projectRecoverModel = new ProjectRecoverModel(projectInfo);

View File

@ -59,4 +59,9 @@ public class ProjectRecoverService extends BaseOperService<ProjectRecoverModel>
public boolean updateItem(ProjectRecoverModel projectRecoverModel) throws Exception {
return false;
}
@Override
public void deleteItem(String id) {
}
}

View File

@ -0,0 +1,28 @@
/**
* 节点项目管理
* <p>
* The MIT License(MIT)
* <p>
* Copyright(c) 2019 码之科技工作室
* <p>
* Permission is hereby granted,free of charge,to any person obtaining a copy of
* this software and associated documentation files(the"Software"),to deal in
* the Software without restriction,including without limitation the rights to
* use,copy,modify,merge,publish,distribute,sublicense,and/or sell copies of
* the Software,and to permit persons to whom the Software is furnished to do so,
* subject to the following conditions:
* <p>
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* <p>
* THE SOFTWARE IS PROVIDED"AS IS",WITHOUT WARRANTY OF ANY KIND,EXPRESS OR
* IMPLIED,INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,FITNESS
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,DAMAGES OR OTHER LIABILITY,WHETHER
* 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.
* <p>
*
* @author jiangzeyin
*/
package cn.keepbx.jpom.service.manage;

View File

@ -0,0 +1,75 @@
package cn.keepbx.jpom.service.script;
import cn.hutool.core.io.FileUtil;
import cn.jiangzeyin.common.DefaultSystemLog;
import cn.keepbx.jpom.common.BaseOperService;
import cn.keepbx.jpom.model.data.ScriptModel;
import cn.keepbx.jpom.system.AgentConfigBean;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.util.List;
/**
* 脚本模板管理
*
* @author jiangzeyin
* @date 2019/4/24
*/
@Service
public class ScriptServer extends BaseOperService<ScriptModel> {
@Override
public List<ScriptModel> list() throws IOException {
JSONObject jsonObject = getJSONObject(AgentConfigBean.SCRIPT);
if (jsonObject == null) {
return null;
}
JSONArray jsonArray = formatToArray(jsonObject);
List<ScriptModel> scriptModels = jsonArray.toJavaList(ScriptModel.class);
if (scriptModels == null) {
return null;
}
// 读取文件内容
scriptModels.forEach(ScriptModel::readFileTime);
return scriptModels;
}
@Override
public ScriptModel getItem(String id) {
ScriptModel scriptModel = getJsonObjectById(AgentConfigBean.SCRIPT, id, ScriptModel.class);
if (scriptModel != null) {
scriptModel.readFileContext();
}
return scriptModel;
}
@Override
public void addItem(ScriptModel scriptModel) {
saveJson(AgentConfigBean.SCRIPT, scriptModel.toJson());
scriptModel.saveFile();
}
@Override
public boolean updateItem(ScriptModel scriptModel) {
try {
updateJson(AgentConfigBean.SCRIPT, scriptModel.toJson());
scriptModel.saveFile();
} catch (Exception e) {
DefaultSystemLog.ERROR().error(e.getMessage(), e);
return false;
}
return true;
}
@Override
public void deleteItem(String id) {
ScriptModel scriptModel = getItem(id);
if (scriptModel != null) {
FileUtil.del(scriptModel.getFile(true).getParentFile());
}
deleteJson(AgentConfigBean.SCRIPT, id);
}
}

View File

@ -56,24 +56,19 @@ public class CertService extends BaseOperService<CertModel> {
*
* @param id id
*/
public boolean delete(String id) {
try {
CertModel certModel = getItem(id);
if (certModel == null) {
return true;
}
String keyPath = certModel.getCert();
deleteJson(AgentConfigBean.CERT, id);
if (StrUtil.isNotEmpty(keyPath)) {
// 删除证书文件
File parentFile = FileUtil.file(keyPath).getParentFile();
FileUtil.del(parentFile);
}
} catch (Exception e) {
DefaultSystemLog.ERROR().error(e.getMessage(), e);
return false;
@Override
public void deleteItem(String id) {
CertModel certModel = getItem(id);
if (certModel == null) {
return;
}
String keyPath = certModel.getCert();
deleteJson(AgentConfigBean.CERT, id);
if (StrUtil.isNotEmpty(keyPath)) {
// 删除证书文件
File parentFile = FileUtil.file(keyPath).getParentFile();
FileUtil.del(parentFile);
}
return true;
}
/**

View File

@ -105,6 +105,11 @@ public class NginxService extends BaseOperService {
return false;
}
@Override
public void deleteItem(String id) {
}
/**
* 获取域名
*

View File

@ -5,6 +5,8 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
/**
* 插件端socket 配置
*
* @author jiangzeyin
* @date 2019/4/19
*/

View File

@ -1,11 +1,10 @@
package cn.keepbx.jpom.socket;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import cn.jiangzeyin.common.DefaultSystemLog;
import cn.jiangzeyin.common.JsonMessage;
import cn.jiangzeyin.common.spring.SpringUtil;
import cn.keepbx.jpom.BaseJpomApplication;
import cn.keepbx.jpom.common.commander.AbstractProjectCommander;
import cn.keepbx.jpom.model.data.ProjectInfoModel;
import cn.keepbx.jpom.service.manage.ConsoleService;
@ -19,26 +18,25 @@ import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
/**
* 插件端socket
* 插件端,控制台socket
*
* @author jiangzeyin
* @date 2019/4/16
*/
@ServerEndpoint(value = "/console/{projectId}/{optUser}")
@Component
public class AgentWebSocketHandle {
public class AgentWebSocketConsoleHandle extends BaseAgentWebSocketHandle {
private static final ConcurrentHashMap<String, String> USER = new ConcurrentHashMap<>();
private static ProjectInfoService projectInfoService;
@OnOpen
public void onOpen(@PathParam("projectId") String projectId, @PathParam("optUser") String urlOptUser, Session session) {
try {
// 判断项目
if (!CommonSocketConfig.SYSTEM_ID.equals(projectId)) {
if (!BaseJpomApplication.SYSTEM_ID.equals(projectId)) {
if (projectInfoService == null) {
projectInfoService = SpringUtil.getBean(ProjectInfoService.class);
}
@ -49,8 +47,7 @@ public class AgentWebSocketHandle {
return;
}
}
String optUser = URLUtil.decode(urlOptUser);
USER.put(session.getId(), optUser);
this.addUser(session, urlOptUser);
} catch (Exception e) {
DefaultSystemLog.ERROR().error("socket 错误", e);
try {
@ -62,16 +59,18 @@ public class AgentWebSocketHandle {
}
}
private String getOptUserName(Session session) {
String name = USER.get(session.getId());
return StrUtil.emptyToDefault(name, StrUtil.DASHED);
}
private boolean silentMsg(CommandOp commandOp, Session session) {
if (commandOp == CommandOp.heart) {
/**
* 静默消息不做过多处理
*
* @param consoleCommandOp 操作
* @param session 回话
* @return true
*/
private boolean silentMsg(ConsoleCommandOp consoleCommandOp, Session session) {
if (consoleCommandOp == ConsoleCommandOp.heart) {
return true;
}
if (commandOp == CommandOp.top) {
if (consoleCommandOp == ConsoleCommandOp.top) {
TopManager.addMonitor(session);
return true;
}
@ -82,32 +81,32 @@ public class AgentWebSocketHandle {
public void onMessage(String message, Session session) throws Exception {
JSONObject json = JSONObject.parseObject(message);
String op = json.getString("op");
CommandOp commandOp = CommandOp.valueOf(op);
if (silentMsg(commandOp, session)) {
ConsoleCommandOp consoleCommandOp = ConsoleCommandOp.valueOf(op);
if (silentMsg(consoleCommandOp, session)) {
return;
}
String projectId = json.getString("projectId");
projectInfoService = SpringUtil.getBean(ProjectInfoService.class);
ProjectInfoModel projectInfoModel = projectInfoService.getItem(projectId);
if (projectInfoModel == null) {
SocketSessionUtil.send(session, "没有对应项目");
session.close();
return;
}
runMsg(commandOp, session, projectInfoModel, json);
runMsg(consoleCommandOp, session, projectInfoModel, json);
}
private void runMsg(CommandOp commandOp, Session session, ProjectInfoModel projectInfoModel, JSONObject reqJson) throws Exception {
private void runMsg(ConsoleCommandOp consoleCommandOp, Session session, ProjectInfoModel projectInfoModel, JSONObject reqJson) throws Exception {
ConsoleService consoleService = SpringUtil.getBean(ConsoleService.class);
JSONObject resultData = null;
String strResult;
boolean logUser = false;
try {
// 执行相应命令
switch (commandOp) {
switch (consoleCommandOp) {
case start:
case restart:
logUser = true;
strResult = consoleService.execCommand(commandOp, projectInfoModel);
strResult = consoleService.execCommand(consoleCommandOp, projectInfoModel);
if (strResult.contains(AbstractProjectCommander.RUNING_TAG)) {
resultData = JsonMessage.toJson(200, "操作成功:" + strResult);
} else {
@ -117,7 +116,7 @@ public class AgentWebSocketHandle {
case stop:
logUser = true;
// 停止项目
strResult = consoleService.execCommand(commandOp, projectInfoModel);
strResult = consoleService.execCommand(consoleCommandOp, projectInfoModel);
if (strResult.contains(AbstractProjectCommander.STOP_TAG)) {
resultData = JsonMessage.toJson(200, "操作成功");
} else {
@ -126,7 +125,7 @@ public class AgentWebSocketHandle {
break;
case status:
// 获取项目状态
strResult = consoleService.execCommand(commandOp, projectInfoModel);
strResult = consoleService.execCommand(consoleCommandOp, projectInfoModel);
if (strResult.contains(AbstractProjectCommander.RUNING_TAG)) {
resultData = JsonMessage.toJson(200, "运行中", strResult);
} else {
@ -144,7 +143,7 @@ public class AgentWebSocketHandle {
break;
}
default:
resultData = JsonMessage.toJson(404, "不支持的方式:" + commandOp.name());
resultData = JsonMessage.toJson(404, "不支持的方式:" + consoleCommandOp.name());
break;
}
} catch (Exception e) {
@ -187,12 +186,8 @@ public class AgentWebSocketHandle {
}
@OnError
@Override
public void onError(Session session, Throwable thr) {
// java.io.IOException: Broken pipe
try {
SocketSessionUtil.send(session, "服务端发生异常" + ExceptionUtil.stacktraceToString(thr));
} catch (IOException ignored) {
}
DefaultSystemLog.ERROR().error(session.getId() + "socket 异常", thr);
super.onError(session, thr);
}
}

View File

@ -0,0 +1,104 @@
package cn.keepbx.jpom.socket;
import cn.hutool.core.util.StrUtil;
import cn.jiangzeyin.common.DefaultSystemLog;
import cn.jiangzeyin.common.JsonMessage;
import cn.jiangzeyin.common.spring.SpringUtil;
import cn.keepbx.jpom.model.data.ScriptModel;
import cn.keepbx.jpom.service.script.ScriptServer;
import cn.keepbx.jpom.util.SocketSessionUtil;
import com.alibaba.fastjson.JSONObject;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
/**
* 脚本模板socket
*
* @author jiangzeyin
* @date 2019/4/24
*/
@ServerEndpoint(value = "/script_run/{id}/{optUser}")
@Component
public class AgentWebSocketScriptHandle extends BaseAgentWebSocketHandle {
private ScriptServer scriptServer;
@OnOpen
public void onOpen(@PathParam("id") String id, Session session, @PathParam("optUser") String urlOptUser) {
if (scriptServer == null) {
scriptServer = SpringUtil.getBean(ScriptServer.class);
}
try {
if (StrUtil.isEmpty(id)) {
SocketSessionUtil.send(session, "脚本模板未知");
return;
}
ScriptModel scriptModel = scriptServer.getItem(id);
if (scriptModel == null) {
SocketSessionUtil.send(session, "没有找到对应的脚本模板");
return;
}
SocketSessionUtil.send(session, "连接成功:" + scriptModel.getName());
this.addUser(session, urlOptUser);
} catch (Exception e) {
DefaultSystemLog.ERROR().error("socket 错误", e);
try {
SocketSessionUtil.send(session, JsonMessage.getString(500, "系统错误!"));
session.close();
} catch (IOException e1) {
DefaultSystemLog.ERROR().error(e1.getMessage(), e1);
}
}
}
@OnMessage
public void onMessage(String message, Session session) throws Exception {
JSONObject json = JSONObject.parseObject(message);
String scriptId = json.getString("scriptId");
ScriptModel scriptModel = scriptServer.getItem(scriptId);
if (scriptModel == null) {
SocketSessionUtil.send(session, "没有对应脚本模板:" + scriptId);
session.close();
return;
}
String op = json.getString("op");
ConsoleCommandOp consoleCommandOp = ConsoleCommandOp.valueOf(op);
switch (consoleCommandOp) {
case start:
String args = json.getString("args");
ScriptProcessBuilder.addWatcher(scriptModel, args, session);
break;
case stop:
ScriptProcessBuilder.stopRun(scriptModel);
break;
case heart:
default:
return;
}
// 记录操作人
scriptModel = scriptServer.getItem(scriptId);
String name = getOptUserName(session);
scriptModel.setLastRunUser(name);
scriptServer.updateItem(scriptModel);
json.put("code", 200);
json.put("msg", "执行成功");
DefaultSystemLog.LOG().info(json.toString());
SocketSessionUtil.send(session, json.toString());
}
@OnClose
public void onClose(Session session) {
ScriptProcessBuilder.stopWatcher(session);
}
@OnError
@Override
public void onError(Session session, Throwable thr) {
super.onError(session, thr);
}
}

View File

@ -0,0 +1,41 @@
package cn.keepbx.jpom.socket;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import cn.jiangzeyin.common.DefaultSystemLog;
import cn.keepbx.jpom.util.SocketSessionUtil;
import javax.websocket.Session;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;
/**
* 插件端socket 基类
*
* @author jiangzeyin
* @date 2019/4/24
*/
public abstract class BaseAgentWebSocketHandle {
protected static final ConcurrentHashMap<String, String> USER = new ConcurrentHashMap<>();
public void addUser(Session session, String name) {
String optUser = URLUtil.decode(name);
USER.put(session.getId(), optUser);
}
public void onError(Session session, Throwable thr) {
// java.io.IOException: Broken pipe
try {
SocketSessionUtil.send(session, "服务端发生异常" + ExceptionUtil.stacktraceToString(thr));
} catch (IOException ignored) {
}
DefaultSystemLog.ERROR().error(session.getId() + "socket 异常", thr);
}
protected String getOptUserName(Session session) {
String name = USER.get(session.getId());
return StrUtil.emptyToDefault(name, StrUtil.DASHED);
}
}

View File

@ -0,0 +1,164 @@
package cn.keepbx.jpom.socket;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IORuntimeException;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.LineHandler;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import cn.jiangzeyin.common.DefaultSystemLog;
import cn.jiangzeyin.common.JsonMessage;
import cn.keepbx.jpom.BaseJpomApplication;
import cn.keepbx.jpom.model.data.ScriptModel;
import cn.keepbx.jpom.util.SocketSessionUtil;
import com.alibaba.fastjson.JSONObject;
import javax.websocket.Session;
import java.io.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
/**
* 脚本执行
*
* @author jiangzeyin
* @date 2019/4/25
*/
public class ScriptProcessBuilder implements Runnable {
private static final ConcurrentHashMap<File, ScriptProcessBuilder> FILE_SCRIPT_PROCESS_BUILDER_CONCURRENT_HASH_MAP = new ConcurrentHashMap<>();
private ProcessBuilder processBuilder;
private Set<Session> sessions = new HashSet<>();
private File logFile;
private File scriptFile;
private Process process;
private InputStream inputStream;
private InputStream errorInputStream;
private ScriptProcessBuilder(ScriptModel scriptModel, String args) {
this.logFile = scriptModel.getLogFile(true);
this.scriptFile = scriptModel.getFile(true);
//
String script = FileUtil.getAbsolutePath(scriptFile);
processBuilder = new ProcessBuilder();
List<String> command = StrUtil.splitTrim(args, StrUtil.SPACE);
command.add(0, script);
DefaultSystemLog.LOG().info(CollUtil.join(command, StrUtil.SPACE));
processBuilder.command(command);
}
public static void addWatcher(ScriptModel scriptModel, String args, Session session) {
File file = scriptModel.getFile(true);
ScriptProcessBuilder scriptProcessBuilder = FILE_SCRIPT_PROCESS_BUILDER_CONCURRENT_HASH_MAP.computeIfAbsent(file, file1 -> {
ScriptProcessBuilder scriptProcessBuilder1 = new ScriptProcessBuilder(scriptModel, args);
ThreadUtil.execute(scriptProcessBuilder1);
return scriptProcessBuilder1;
});
if (scriptProcessBuilder.sessions.add(session)) {
if (FileUtil.exist(scriptProcessBuilder.logFile)) {
// 读取之前的信息并发送
FileUtil.readLines(scriptProcessBuilder.logFile, CharsetUtil.CHARSET_UTF_8, (LineHandler) line -> {
try {
SocketSessionUtil.send(session, line);
} catch (IOException e) {
DefaultSystemLog.ERROR().error("发送消息失败", e);
}
});
}
}
}
public static void stopWatcher(Session session) {
Collection<ScriptProcessBuilder> scriptProcessBuilders = FILE_SCRIPT_PROCESS_BUILDER_CONCURRENT_HASH_MAP.values();
for (ScriptProcessBuilder scriptProcessBuilder : scriptProcessBuilders) {
Set<Session> sessions = scriptProcessBuilder.sessions;
sessions.removeIf(session1 -> session1.getId().equals(session.getId()));
}
}
public static void stopRun(ScriptModel scriptModel) {
File file = scriptModel.getFile(true);
ScriptProcessBuilder scriptProcessBuilder = FILE_SCRIPT_PROCESS_BUILDER_CONCURRENT_HASH_MAP.get(file);
if (scriptProcessBuilder != null) {
scriptProcessBuilder.end("停止运行");
}
}
@Override
public void run() {
//初始化ProcessBuilder对象
try {
process = processBuilder.start();
{
inputStream = process.getInputStream();
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, BaseJpomApplication.getCharset());
BufferedReader results = new BufferedReader(inputStreamReader);
IoUtil.readLines(results, (LineHandler) ScriptProcessBuilder.this::handle);
}
{
errorInputStream = process.getErrorStream();
InputStreamReader inputStreamReader = new InputStreamReader(errorInputStream, BaseJpomApplication.getCharset());
BufferedReader results = new BufferedReader(inputStreamReader);
IoUtil.readLines(results, (LineHandler) line -> ScriptProcessBuilder.this.handle("ERROR:" + line));
}
JsonMessage jsonMessage = new JsonMessage(200, "执行完毕");
JSONObject jsonObject = jsonMessage.toJson();
jsonObject.put("op", ConsoleCommandOp.stop.name());
this.end(jsonObject.toString());
} catch (IORuntimeException ignored) {
} catch (Exception e) {
DefaultSystemLog.ERROR().error("执行异常", e);
this.end("执行异常:" + e.getMessage());
}
}
/**
* 结束执行
*
* @param msg 响应的消息
*/
private void end(String msg) {
if (this.process != null) {
// windows 中不能正常关闭
this.process.destroy();
IoUtil.close(inputStream);
IoUtil.close(errorInputStream);
}
Iterator<Session> iterator = sessions.iterator();
while (iterator.hasNext()) {
Session session = iterator.next();
try {
SocketSessionUtil.send(session, msg);
} catch (IOException e) {
DefaultSystemLog.ERROR().error("发送消息失败", e);
}
iterator.remove();
}
FILE_SCRIPT_PROCESS_BUILDER_CONCURRENT_HASH_MAP.remove(this.scriptFile);
}
/**
* 响应
*
* @param line 信息
*/
private void handle(String line) {
// 写入文件
List<String> fileLine = new ArrayList<>();
fileLine.add(line);
FileUtil.appendLines(fileLine, logFile, CharsetUtil.CHARSET_UTF_8);
Iterator<Session> iterator = sessions.iterator();
while (iterator.hasNext()) {
Session session = iterator.next();
try {
SocketSessionUtil.send(session, line);
} catch (IOException e) {
DefaultSystemLog.ERROR().error("发送消息失败", e);
iterator.remove();
}
}
}
}

View File

@ -3,11 +3,9 @@ package cn.keepbx.jpom.system;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import cn.jiangzeyin.common.spring.SpringUtil;
import cn.jiangzeyin.controller.base.AbstractController;
import cn.keepbx.jpom.common.BaseAgentController;
import org.springframework.context.annotation.Configuration;
import javax.servlet.http.HttpServletRequest;
import java.io.File;
/**
@ -22,26 +20,30 @@ public class AgentConfigBean {
* 白名单文件
*/
public static final String WHITELIST_DIRECTORY = "whitelistDirectory.json";
/**
* 项目数据文件
*/
public static final String PROJECT = "project.json";
/**
* 项目回收文件
*/
public static final String PROJECT_RECOVER = "project_recover.json";
/**
* 阿里oss 文件
*/
public static final String ALI_OSS = "aliOss.json";
/**
* 证书文件
*/
public static final String CERT = "cert.json";
/**
* 脚本管理数据文件
*/
public static final String SCRIPT = "script.json";
/**
* 脚本模板存放路径
*/
public static final String SCRIPT_DIRECTORY = "script";
private static AgentConfigBean agentConfigBean;
@ -74,8 +76,7 @@ public class AgentConfigBean {
*/
public File getTempPath() {
File file = new File(ConfigBean.getInstance().getDataPath());
HttpServletRequest request = AbstractController.getRequestAttributes().getRequest();
String userName = BaseAgentController.getUserName(request);
String userName = BaseAgentController.getNowUserName();
if (StrUtil.isEmpty(userName)) {
throw new JpomRuntimeException("没有登录");
}
@ -83,4 +84,13 @@ public class AgentConfigBean {
FileUtil.mkdir(file);
return file;
}
/**
* 获取脚本模板路径
*
* @return file
*/
public File getScriptPath() {
return FileUtil.file(ExtConfigBean.getInstance().getPath(), SCRIPT_DIRECTORY);
}
}

View File

@ -14,7 +14,10 @@ import java.nio.charset.Charset;
* @date 2019/4/16
*/
public abstract class BaseJpomApplication {
/**
*
*/
public static final String SYSTEM_ID = "system";
public static final OsInfo OS_INFO = SystemUtil.getOsInfo();
protected static String[] args;

View File

@ -38,6 +38,13 @@ public abstract class BaseOperService<T> extends BaseDataService {
*/
public abstract void addItem(T t);
/**
* 删除实体
*
* @param id 数据id
*/
public abstract void deleteItem(String id);
/**
* 修改实体
*

View File

@ -1,11 +0,0 @@
package cn.keepbx.jpom.socket;
/**
* @author jiangzeyin
* @date 2019/4/19
*/
public class CommonSocketConfig {
public static final String SYSTEM_ID = "system";
}

View File

@ -1,12 +1,12 @@
package cn.keepbx.jpom.socket;
/**
* socket 操作枚举
* 控制台socket 操作枚举
*
* @author jiangzeyin
* @date 2019/4/16
*/
public enum CommandOp {
public enum ConsoleCommandOp {
/**
* 启动
*/

View File

@ -22,6 +22,10 @@ public enum NodeUrl {
* socket 连接 第一节项目id 第二节用户信息
*/
TopSocket("/console/{}/{}"),
/**
* 脚本模板 模板id
*/
Script_Run("/script_run/{}/{}"),
WhitelistDirectory_Submit("/system/whitelistDirectory_submit"),
@ -93,13 +97,17 @@ public enum NodeUrl {
System_Nginx_delete("/system/nginx/delete"),
System_Certificate_saveCertificate("/system/certificate/saveCertificate"),
System_Certificate_getCertList("/system/certificate/getCertList"),
System_Certificate_delete("/system/certificate/delete"),
System_Certificate_export("/system/certificate/export"),
Script_List("/script/list.json"),
Script_Item("/script/item.json"),
Script_Save("/script/save.json"),
Script_Upload("/script/upload.json"),
Script_Del("/script/del.json"),
;
/**
* 相对请求地址

View File

@ -4,12 +4,12 @@ import cn.hutool.core.lang.Validator;
import cn.hutool.core.util.StrUtil;
import cn.jiangzeyin.common.DefaultSystemLog;
import cn.jiangzeyin.common.JsonMessage;
import cn.keepbx.jpom.BaseJpomApplication;
import cn.keepbx.jpom.common.BaseServerController;
import cn.keepbx.jpom.common.interceptor.LoginInterceptor;
import cn.keepbx.jpom.common.interceptor.NotLogin;
import cn.keepbx.jpom.model.data.UserModel;
import cn.keepbx.jpom.service.user.UserService;
import cn.keepbx.jpom.socket.CommonSocketConfig;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
@ -59,7 +59,7 @@ public class InstallController extends BaseServerController {
if (userName.length() < UserModel.USER_NAME_MIN_LEN) {
return JsonMessage.getString(400, "登录名长度必须不小于" + UserModel.USER_NAME_MIN_LEN);
}
if (CommonSocketConfig.SYSTEM_ID.equalsIgnoreCase(userName)) {
if (BaseJpomApplication.SYSTEM_ID.equalsIgnoreCase(userName)) {
return JsonMessage.getString(400, "当前登录名已经被系统占用啦");
}
if (Validator.isChinese(userName) || !checkPathSafe(userName)) {

View File

@ -120,10 +120,7 @@ public class NodeEditController extends BaseServerController {
@UrlPermission(value = Role.System, optType = UserOperateLogV1.OptType.DelNode)
@ResponseBody
public String save(String id) {
boolean flag = nodeService.deleteItem(id);
if (!flag) {
return JsonMessage.getString(405, "删除失败");
}
nodeService.deleteItem(id);
// 删除授权
List<UserModel> list = userService.list();
if (list != null) {

View File

@ -0,0 +1,84 @@
package cn.keepbx.jpom.controller.node.script;
import cn.hutool.core.util.StrUtil;
import cn.keepbx.jpom.common.BaseServerController;
import cn.keepbx.jpom.common.forward.NodeForward;
import cn.keepbx.jpom.common.forward.NodeUrl;
import cn.keepbx.jpom.common.interceptor.UrlPermission;
import cn.keepbx.jpom.model.Role;
import cn.keepbx.jpom.model.data.UserOperateLogV1;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
/**
* 脚本管理
*
* @author jiangzeyin
* @date 2019/4/24
*/
@Controller
@RequestMapping(value = "/node/script")
public class ScriptController extends BaseServerController {
@RequestMapping(value = "list.html", method = RequestMethod.GET, produces = MediaType.TEXT_HTML_VALUE)
public String list() {
JSONArray jsonArray = NodeForward.requestData(getNode(), NodeUrl.Script_List, getRequest(), JSONArray.class);
setAttribute("array", jsonArray);
return "node/script/list";
}
@RequestMapping(value = "item.html", method = RequestMethod.GET, produces = MediaType.TEXT_HTML_VALUE)
public String item(String id) {
setAttribute("type", "add");
if (StrUtil.isNotEmpty(id)) {
JSONObject jsonObject = NodeForward.requestData(getNode(), NodeUrl.Script_Item, getRequest(), JSONObject.class);
if (jsonObject != null) {
setAttribute("type", "edit");
setAttribute("item", jsonObject);
}
}
return "node/script/edit";
}
/**
* 保存脚本
*
* @return json
*/
@RequestMapping(value = "save.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
@ResponseBody
@UrlPermission(value = Role.System, optType = UserOperateLogV1.OptType.Save_Script)
public String save() {
return NodeForward.request(getNode(), getRequest(), NodeUrl.Script_Save).toString();
}
@RequestMapping(value = "del.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
@ResponseBody
@UrlPermission(value = Role.System, optType = UserOperateLogV1.OptType.Save_Del)
public String del() {
return NodeForward.request(getNode(), getRequest(), NodeUrl.Script_Del).toString();
}
/**
* 导入脚本
*
* @return json
*/
@RequestMapping(value = "upload", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
@ResponseBody
@UrlPermission(value = Role.System, optType = UserOperateLogV1.OptType.Save_Upload)
public String upload() {
return NodeForward.requestMultipart(getNode(), getMultiRequest(), NodeUrl.Script_Upload).toString();
}
@RequestMapping(value = "console.html", method = RequestMethod.GET, produces = MediaType.TEXT_HTML_VALUE)
public String console(String id) {
return "node/script/console";
}
}

View File

@ -196,10 +196,7 @@ public class OutGivingController extends BaseServerController {
@ResponseBody
@UrlPermission(value = Role.ServerManager, optType = UserOperateLogV1.OptType.DelOutGiving)
public String del(String id) {
boolean flag = outGivingServer.deleteItem(id);
if (!flag) {
return JsonMessage.getString(405, "删除失败");
}
outGivingServer.deleteItem(id);
return JsonMessage.getString(200, "操作成功");
}
}

View File

@ -4,6 +4,7 @@ import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.StrUtil;
import cn.jiangzeyin.common.DefaultSystemLog;
import cn.jiangzeyin.common.JsonMessage;
import cn.keepbx.jpom.BaseJpomApplication;
import cn.keepbx.jpom.common.BaseServerController;
import cn.keepbx.jpom.common.interceptor.LoginInterceptor;
import cn.keepbx.jpom.common.interceptor.UrlPermission;
@ -12,7 +13,6 @@ import cn.keepbx.jpom.model.data.NodeModel;
import cn.keepbx.jpom.model.data.UserModel;
import cn.keepbx.jpom.model.data.UserOperateLogV1;
import cn.keepbx.jpom.service.user.UserService;
import cn.keepbx.jpom.socket.CommonSocketConfig;
import cn.keepbx.jpom.system.ServerExtConfigBean;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
@ -122,11 +122,8 @@ public class UserInfoController extends BaseServerController {
if (userModel.isSystemUser()) {
return JsonMessage.getString(400, "非法访问:-5");
}
boolean b = userService.deleteUser(id);
if (b) {
return JsonMessage.getString(200, "删除成功");
}
return JsonMessage.getString(400, "删除失败");
userService.deleteItem(id);
return JsonMessage.getString(200, "删除成功");
}
/**
@ -138,7 +135,7 @@ public class UserInfoController extends BaseServerController {
@RequestMapping(value = "addUser", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_UTF8_VALUE)
@UrlPermission(value = Role.ServerManager, optType = UserOperateLogV1.OptType.AddUer)
public String addUser(String id) {
if (CommonSocketConfig.SYSTEM_ID.equalsIgnoreCase(id)) {
if (BaseJpomApplication.SYSTEM_ID.equalsIgnoreCase(id)) {
return JsonMessage.getString(400, "当前登录名已经被系统占用啦");
}
UserModel userName = getUser();

View File

@ -15,8 +15,10 @@ import cn.hutool.db.sql.Order;
import cn.jiangzeyin.common.JsonMessage;
import cn.keepbx.jpom.common.BaseServerController;
import cn.keepbx.jpom.model.data.NodeModel;
import cn.keepbx.jpom.model.data.UserModel;
import cn.keepbx.jpom.model.data.UserOperateLogV1;
import cn.keepbx.jpom.service.node.NodeService;
import cn.keepbx.jpom.service.user.UserService;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import org.springframework.http.MediaType;
@ -40,6 +42,8 @@ import java.util.List;
public class UserOptLogController extends BaseServerController {
@Resource
private NodeService nodeService;
@Resource
private UserService userService;
/**
* 展示用户列表
@ -49,6 +53,9 @@ public class UserOptLogController extends BaseServerController {
// 所有节点
List<NodeModel> nodeModels = nodeService.list();
setAttribute("nodeArray", nodeModels);
// 用户
List<UserModel> userModels = userService.list();
setAttribute("userArray", userModels);
return "user/log/list";
}
@ -83,6 +90,11 @@ public class UserOptLogController extends BaseServerController {
entity.set("nodeId".toUpperCase(), selectNode);
}
String selectUser = getParameter("selectUser");
if (StrUtil.isNotEmpty(selectUser)) {
entity.set("userId".toUpperCase(), selectUser);
}
PageResult<Entity> pageResult = Db.use().page(entity, page);
CopyOptions copyOptions = new CopyOptions();
copyOptions.setIgnoreError(true);

View File

@ -3,10 +3,10 @@ package cn.keepbx.jpom.model.data;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.crypto.SecureUtil;
import cn.keepbx.jpom.BaseJpomApplication;
import cn.keepbx.jpom.model.BaseJsonModel;
import cn.keepbx.jpom.model.BaseModel;
import cn.keepbx.jpom.model.Role;
import cn.keepbx.jpom.socket.CommonSocketConfig;
import cn.keepbx.jpom.system.ServerExtConfigBean;
import com.alibaba.fastjson.JSONArray;
@ -253,7 +253,7 @@ public class UserModel extends BaseModel {
return true;
}
// 系统监控权限
if (CommonSocketConfig.SYSTEM_ID.equals(id)) {
if (BaseJpomApplication.SYSTEM_ID.equals(id)) {
return true;
}
NodeRole item = nodeRole.get(nodeId);
@ -420,7 +420,7 @@ public class UserModel extends BaseModel {
public boolean isManage() {
return manage;
}
public JSONArray getProjects() {
return projects;
}

View File

@ -2,6 +2,7 @@ package cn.keepbx.jpom.model.data;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.keepbx.jpom.BaseJpomApplication;
import cn.keepbx.jpom.model.BaseEnum;
import cn.keepbx.jpom.model.BaseJsonModel;
@ -126,7 +127,11 @@ public class UserOperateLogV1 extends BaseJsonModel {
}
public void setUserId(String userId) {
this.userId = userId;
if (UserModel.SYSTEM_OCCUPY_NAME.equals(userId)) {
this.userId = BaseJpomApplication.SYSTEM_ID;
} else {
this.userId = userId;
}
}
public long getOptTime() {
@ -232,6 +237,13 @@ public class UserOperateLogV1 extends BaseJsonModel {
SaveOutgivingWhitelist(36, "修改节点白名单"),
SaveOutgivingProject(37, "保存节点分发项目"),
DeleteOutgivingProject(38, "删除节点分发项目"),
Save_Script(39, "保存脚本模板"),
Script_Start(40, "执行脚本"),
Script_Stop(41, "停止脚本"),
Save_Upload(34, "导入脚本模板"),
Save_Del(34, "删除脚本模板"),
;
private int code;
private String desc;

View File

@ -2,7 +2,6 @@ package cn.keepbx.jpom.service.node;
import cn.hutool.cache.impl.TimedCache;
import cn.hutool.core.util.IdUtil;
import cn.jiangzeyin.common.DefaultSystemLog;
import cn.keepbx.jpom.common.BaseOperService;
import cn.keepbx.jpom.common.forward.NodeForward;
import cn.keepbx.jpom.common.forward.NodeUrl;
@ -96,13 +95,8 @@ public class NodeService extends BaseOperService<NodeModel> {
return true;
}
public boolean deleteItem(String id) {
try {
deleteJson(ServerConfigBean.NODE, id);
return true;
} catch (Exception e) {
DefaultSystemLog.ERROR().error(e.getMessage(), e);
}
return false;
@Override
public void deleteItem(String id) {
deleteJson(ServerConfigBean.NODE, id);
}
}

View File

@ -1,6 +1,5 @@
package cn.keepbx.jpom.service.node;
import cn.jiangzeyin.common.DefaultSystemLog;
import cn.keepbx.jpom.common.BaseOperService;
import cn.keepbx.jpom.model.data.OutGivingModel;
import cn.keepbx.jpom.system.ServerConfigBean;
@ -54,13 +53,8 @@ public class OutGivingServer extends BaseOperService<OutGivingModel> {
return true;
}
public boolean deleteItem(String id) {
try {
deleteJson(ServerConfigBean.OUTGIVING, id);
return true;
} catch (Exception e) {
DefaultSystemLog.ERROR().error(e.getMessage(), e);
}
return false;
@Override
public void deleteItem(String id) {
deleteJson(ServerConfigBean.OUTGIVING, id);
}
}

View File

@ -128,16 +128,10 @@ public class UserService extends BaseOperService<UserModel> {
* 删除用户
*
* @param id 用户id
* @return String
*/
public boolean deleteUser(String id) {
try {
deleteJson(ServerConfigBean.USER, id);
return true;
} catch (Exception e) {
DefaultSystemLog.ERROR().error(e.getMessage(), e);
}
return false;
@Override
public void deleteItem(String id) {
deleteJson(ServerConfigBean.USER, id);
}
/**

View File

@ -1,6 +1,5 @@
package cn.keepbx.jpom.socket;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import cn.jiangzeyin.common.DefaultSystemLog;
@ -22,27 +21,32 @@ import java.io.IOException;
import java.util.Map;
/**
* 消息处理器
* 服务端socket 基本类
*
* @author jiangzeyin
* @date 2019/4/19
* @date 2019/4/25
*/
public class ServerWebSocketHandler extends TextWebSocketHandler {
private OperateLogController operateLogController;
public abstract class BaseServerWebSocketHandler extends TextWebSocketHandler {
protected OperateLogController operateLogController;
private NodeUrl nodeUrl;
private String dataParName;
public BaseServerWebSocketHandler(NodeUrl nodeUrl, String dataParName) {
this.nodeUrl = nodeUrl;
this.dataParName = dataParName;
}
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
if (operateLogController == null) {
operateLogController = SpringUtil.getBean(OperateLogController.class);
}
Map<String, Object> attributes = session.getAttributes();
NodeModel nodeModel = (NodeModel) attributes.get("nodeInfo");
String projectId = (String) attributes.get("projectId");
UserModel userInfo = (UserModel) attributes.get("userInfo");
String url = NodeForward.getSocketUrl(nodeModel, NodeUrl.TopSocket);
String dataValue = (String) attributes.get(dataParName);
String userName = UserModel.getOptUserName(userInfo);
userName = URLUtil.encode(userName);
url = StrUtil.format(url, projectId, userName);
String url = NodeForward.getSocketUrl(nodeModel, nodeUrl);
url = StrUtil.format(url, dataValue, userName);
// 连接节点
ProxySession proxySession = new ProxySession(url, session);
session.getAttributes().put("proxySession", proxySession);
@ -51,56 +55,44 @@ public class ServerWebSocketHandler extends TextWebSocketHandler {
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) {
if (operateLogController == null) {
operateLogController = SpringUtil.getBean(OperateLogController.class);
}
String msg = message.getPayload();
Map<String, Object> attributes = session.getAttributes();
ProxySession proxySession = (ProxySession) attributes.get("proxySession");
JSONObject json = JSONObject.parseObject(msg);
String op = json.getString("op");
CommandOp commandOp = CommandOp.valueOf(op);
UserOperateLogV1.OptType type = null;
switch (commandOp) {
case stop:
type = UserOperateLogV1.OptType.Stop;
break;
case start:
type = UserOperateLogV1.OptType.Start;
break;
case restart:
type = UserOperateLogV1.OptType.Restart;
break;
default:
break;
}
if (type != null) {
// 记录操作日志
UserModel userInfo = (UserModel) attributes.get("userInfo");
String ip = (String) attributes.get("ip");
NodeModel nodeModel = (NodeModel) attributes.get("nodeInfo");
//
String projectId = (String) attributes.get("projectId");
ConsoleCommandOp consoleCommandOp = ConsoleCommandOp.valueOf(op);
this.handleTextMessage(attributes, proxySession, json, consoleCommandOp);
}
String reqId = IdUtil.fastUUID();
json.put("reqId", reqId);
/**
* 消息处理方法
*
* @param attributes 属性
* @param proxySession 代理回话
* @param json 数据
* @param consoleCommandOp 操作类型
*/
protected abstract void handleTextMessage(Map<String, Object> attributes,
ProxySession proxySession,
JSONObject json,
ConsoleCommandOp consoleCommandOp);
try {
OperateLogController.CacheInfo cacheInfo = new OperateLogController.CacheInfo();
cacheInfo.setIp(ip);
cacheInfo.setOptType(type);
cacheInfo.setNodeModel(nodeModel);
cacheInfo.setDataId(projectId);
String userAgent = (String) attributes.get(HttpHeaders.USER_AGENT);
cacheInfo.setUserAgent(userAgent);
protected OperateLogController.CacheInfo cacheInfo(Map<String, Object> attributes, JSONObject json, UserOperateLogV1.OptType optType, String dataId) {
String ip = (String) attributes.get("ip");
NodeModel nodeModel = (NodeModel) attributes.get("nodeInfo");
OperateLogController.CacheInfo cacheInfo = new OperateLogController.CacheInfo();
cacheInfo.setIp(ip);
cacheInfo.setOptType(optType);
cacheInfo.setNodeModel(nodeModel);
cacheInfo.setDataId(dataId);
String userAgent = (String) attributes.get(HttpHeaders.USER_AGENT);
cacheInfo.setUserAgent(userAgent);
cacheInfo.setReqData(json.toString());
operateLogController.log(reqId, userInfo, "还没有响应", cacheInfo);
} catch (Exception e) {
DefaultSystemLog.ERROR().error("记录操作日志异常", e);
}
proxySession.send(json.toString());
} else {
proxySession.send(msg);
}
cacheInfo.setReqData(json.toString());
return cacheInfo;
}
@Override

View File

@ -93,4 +93,13 @@ public class ProxySession extends WebSocketClient {
}
DefaultSystemLog.ERROR().error("发生错误", ex);
}
@Override
public void send(String text) {
try {
super.send(text);
} catch (Exception e) {
DefaultSystemLog.ERROR().error("转发消息失败", e);
}
}
}

View File

@ -13,10 +13,15 @@ import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry
@Configuration
@EnableWebSocket
public class ServerWebSocketConfig implements WebSocketConfigurer {
private final ServerWebSocketInterceptor serverWebSocketInterceptor = new ServerWebSocketInterceptor();
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(new ServerWebSocketHandler(), "/console")
.addInterceptors(new ServerWebSocketInterceptor()).setAllowedOrigins("*");
// 控制台
registry.addHandler(new ServerWebSocketConsoleHandler(), "/console")
.addInterceptors(serverWebSocketInterceptor).setAllowedOrigins("*");
// 脚本模板
registry.addHandler(new ServerWebSocketScriptHandler(), "/script_run")
.addInterceptors(serverWebSocketInterceptor).setAllowedOrigins("*");
}
}

View File

@ -0,0 +1,57 @@
package cn.keepbx.jpom.socket;
import cn.hutool.core.util.IdUtil;
import cn.jiangzeyin.common.DefaultSystemLog;
import cn.keepbx.jpom.common.forward.NodeUrl;
import cn.keepbx.jpom.model.data.UserModel;
import cn.keepbx.jpom.model.data.UserOperateLogV1;
import cn.keepbx.jpom.system.init.OperateLogController;
import com.alibaba.fastjson.JSONObject;
import java.util.Map;
/**
* 控制台消息处理器
*
* @author jiangzeyin
* @date 2019/4/19
*/
public class ServerWebSocketConsoleHandler extends BaseServerWebSocketHandler {
public ServerWebSocketConsoleHandler() {
super(NodeUrl.TopSocket, "projectId");
}
@Override
protected void handleTextMessage(Map<String, Object> attributes, ProxySession proxySession, JSONObject json, ConsoleCommandOp consoleCommandOp) {
UserOperateLogV1.OptType type = null;
switch (consoleCommandOp) {
case stop:
type = UserOperateLogV1.OptType.Stop;
break;
case start:
type = UserOperateLogV1.OptType.Start;
break;
case restart:
type = UserOperateLogV1.OptType.Restart;
break;
default:
break;
}
if (type != null) {
// 记录操作日志
UserModel userInfo = (UserModel) attributes.get("userInfo");
String reqId = IdUtil.fastUUID();
json.put("reqId", reqId);
//
String projectId = (String) attributes.get("projectId");
OperateLogController.CacheInfo cacheInfo = cacheInfo(attributes, json, type, projectId);
try {
operateLogController.log(reqId, userInfo, "还没有响应", cacheInfo);
} catch (Exception e) {
DefaultSystemLog.ERROR().error("记录操作日志异常", e);
}
}
proxySession.send(json.toString());
}
}

View File

@ -37,22 +37,37 @@ public class ServerWebSocketInterceptor implements HandshakeInterceptor {
return false;
}
String nodeId = httpServletRequest.getParameter("nodeId");
String projectId = httpServletRequest.getParameter("projectId");
NodeService nodeService = SpringUtil.getBean(NodeService.class);
NodeModel nodeModel = nodeService.getItem(nodeId);
// 判断权限
if (!userModel.isProject(nodeModel.getId(), projectId)) {
if (nodeModel == null) {
return false;
}
attributes.put("userInfo", userModel);
attributes.put("nodeInfo", nodeModel);
attributes.put("projectId", projectId);
// 判断拦截类型
String type = httpServletRequest.getParameter("type");
if ("script".equalsIgnoreCase(type)) {
// 脚本模板
String scriptId = httpServletRequest.getParameter("scriptId");
if (!userModel.isManage(nodeId)) {
return false;
}
attributes.put("scriptId", scriptId);
} else {
//控制台
String projectId = httpServletRequest.getParameter("projectId");
// 判断权限
if (!userModel.isProject(nodeModel.getId(), projectId)) {
return false;
}
attributes.put("projectId", projectId);
}
//
String ip = ServletUtil.getClientIP(httpServletRequest);
attributes.put("ip", ip);
//
String userAgent = ServletUtil.getHeaderIgnoreCase(httpServletRequest, HttpHeaders.USER_AGENT);
attributes.put(HttpHeaders.USER_AGENT, userAgent);
attributes.put("nodeInfo", nodeModel);
attributes.put("userInfo", userModel);
return true;
}
return false;

View File

@ -0,0 +1,51 @@
package cn.keepbx.jpom.socket;
import cn.jiangzeyin.common.DefaultSystemLog;
import cn.keepbx.jpom.common.forward.NodeUrl;
import cn.keepbx.jpom.model.data.UserModel;
import cn.keepbx.jpom.model.data.UserOperateLogV1;
import cn.keepbx.jpom.system.init.OperateLogController;
import com.alibaba.fastjson.JSONObject;
import java.util.Map;
/**
* 脚本模板消息控制器
*
* @author jiangzeyin
* @date 2019/4/24
*/
public class ServerWebSocketScriptHandler extends BaseServerWebSocketHandler {
public ServerWebSocketScriptHandler() {
super(NodeUrl.Script_Run, "scriptId");
}
@Override
protected void handleTextMessage(Map<String, Object> attributes, ProxySession proxySession, JSONObject json, ConsoleCommandOp consoleCommandOp) {
UserOperateLogV1.OptType type = null;
switch (consoleCommandOp) {
case stop:
type = UserOperateLogV1.OptType.Script_Stop;
break;
case start:
type = UserOperateLogV1.OptType.Script_Start;
break;
default:
break;
}
if (type != null) {
// 记录操作日志
UserModel userInfo = (UserModel) attributes.get("userInfo");
//
String scriptId = (String) attributes.get("scriptId");
OperateLogController.CacheInfo cacheInfo = cacheInfo(attributes, json, type, scriptId);
try {
operateLogController.log(userInfo, "脚本模板执行...", cacheInfo);
} catch (Exception e) {
DefaultSystemLog.ERROR().error("记录操作日志异常", e);
}
}
proxySession.send(json.toString());
}
}

View File

@ -63,14 +63,17 @@
<a href="javascript:;"
data-options="{'id':'welcome', 'title':'节点管理', 'url':'./node/list.html'}">节点管理</a>
</li>
<li class="layui-nav-item">
<a href="javascript:;"
data-options="{'id':'outgiving', 'title':'节点分发', 'url':'./outgiving/list.html'}">节点分发</a>
</li>
<li class="layui-nav-item">
<a href="javascript:;"
data-options="{'id':'user', 'title':'用户管理', 'url':'./user/list'}">用户管理</a>
</li>
#if($user.isServerManager())
<li class="layui-nav-item">
<a href="javascript:;"
data-options="{'id':'user', 'title':'用户管理', 'url':'./user/list'}">用户管理</a>
</li>
#end
<li class="layui-nav-item">
<a href="javascript:;"
data-options="{'id':'user_log', 'title':'操作日志', 'url':'./user/log/list.html'}">操作日志</a>

View File

@ -72,15 +72,20 @@
<a href="javascript:;"
data-options="{'id':'manage', 'title':'项目管理', 'url':'./manage/projectInfo'}">项目管理</a>
</li>
## <li class="layui-nav-item">
## <a href="javascript:;"
## data-options="{'id':'downtime', 'title':'宕机计划', 'url':'./manage/projectInfo'}">宕机计划</a>
## </li>
## <li class="layui-nav-item">
## <a href="javascript:;"
## data-options="{'id':'downtime', 'title':'宕机计划', 'url':'./manage/projectInfo'}">宕机计划</a>
## </li>
#if($user.isManage($node.id))
<li class="layui-nav-item">
<a href="javascript:;"
data-options="{'id':'nginx', 'title':'nginx管理', 'url':'./system/nginx/list.html'}">nginx管理</a>
</li>
<li class="layui-nav-item">
<a href="javascript:;"
data-options="{'id':'script', 'title':'脚本模板', 'url':'./script/list.html'}">脚本模板</a>
</li>
#end
#if($user.isSystemUser())
<li class="layui-nav-item">

View File

@ -61,6 +61,7 @@
var config = {
cols: [[
{field: 'osName', title: '系统名', width: "10%"},
{field: 'javaVersion', title: 'Java版本', width: "10%"},
{field: 'jpomVersion', title: 'Jpom版本', width: "10%"},
{field: 'count', title: '项目个数', width: "10%"},
{field: 'runCount', title: '运行中个数', width: "10%"},

View File

@ -0,0 +1,222 @@
<!DOCTYPE html>
<html>
<head>
#parse("./common/head.vm")
<title>项目管理系统</title>
<style>
body {
position: absolute;
top: 0px;
right: 0px;
bottom: 0px;
left: 0px;
padding: 10px;
}
.console {
height: 100%;
position: relative;
}
.console .terminal {
position: absolute;
top: 40px;
right: 0px;
bottom: 0px;
left: 0px;
border: 1px solid #c1c1c1;
border-radius: 5px;
font-family: Consolas;
padding: 5px;
overflow: auto;
word-break: keep-all;
white-space: nowrap;
}
.console .status {
display: inline-block;
float: right;
}
.console .status .status-div {
display: inline-block;
width: 10px;
height: 10px;
border-radius: 50%;
border: 1px solid #c1c1c1;
}
.console .status .status-run {
background-color: #05ff2d;
}
.console .status .status-stop {
background-color: #ff0000;
}
</style>
</head>
<body>
<div class="console">
#if($user.isManage($node.id))
<div class="layui-row" id="optDiv">
<!-- layui-elem-quote -->
<a href="javascript:;" class="btn-op layui-btn layui-btn-sm" op="start">执行</a>
## <a href="javascript:;" class="btn-op layui-btn layui-btn-sm layui-btn-warm" op="restart">重启</a>
<a href="javascript:;" style="display: none;" class="btn-op layui-btn layui-btn-sm layui-btn-danger"
op="stop">停止</a>
<div class="status">
<div class="status-div"></div>
<span></span>
</div>
</div>
#end
<div class="terminal"></div>
</div>
</body>
<script type="text/javascript">
var heart;
var scriptId = getQueryString("id");
function loadSuccess() {
const showLogDom = $('.console .terminal');
if ('WebSocket' in window) {
var url = getSocketHost() + "/script_run?userId=$user.getUserMd5Key()&scriptId=" + scriptId + "&nodeId=$node.id&type=script";
const ws = new WebSocket(url);
ws.onopen = function () {
showLogDom.append('WebSocket连接成功<br/>');
};
ws.onmessage = function (data) {
// 如果是
if (data.data.indexOf('{') === 0) {
layer.closeAll();
var json_data = null;
try {
json_data = JSON.parse(data.data);
} catch (e) {
showLogDom.append(data.data + '<br/>');
return;
}
if (json_data.code !== 200) {
layer.msg(json_data.msg);
}
showLogDom.append(json_data.msg + (json_data.data || "") + '<br/>');
var op = json_data.op;
switch (op) {
case 'start':
if (200 === json_data.code) {
$('.status .status-div').removeClass('status-stop').addClass('status-run');
$('.status span').text('运行中');
setOpBtn(false);
} else {
$('.status .status-div').removeClass('status-run').addClass('status-stop');
$('.status span').text(json_data.msg);
setOpBtn(true);
}
break;
case 'stop':
if (200 === json_data.code) {
$('.status .status-div').removeClass('status-run').addClass('status-stop');
$('.status span').text('未运行');
setOpBtn(true);
} else {
$('.status span').text(json_data.msg);
}
break;
default:
break;
}
} else {
if (data.data) {
showLogDom.append(data.data + '<br/>');
}
clearInterval(heart);
// 创建心跳,防止掉线
heart = setInterval(function () {
const data = {
op: "heart",
scriptId: scriptId
};
ws.send(JSON.stringify(data));
}, 5000);
}
scrollToBotomm();
};
ws.onclose = function (er) {
showLogDom.append('WebSocket连接已关闭<br/>');
clearInterval(heart);
$("#optDiv").hide();
layer.alert("控制台已离线")
};
$('.btn-op').on('click', function () {
var op = $(this).attr('op');
if (op == "stop") {
layer.confirm("确定要停止该脚本吗?", {
title: '系统提示'
}, function (index) {
layer.close(index);
setMsg(op);
});
} else if (op == "start") {
//
layer.prompt({
title: '请输入执行参数',
formType: 2,
value: ' '
}, function (text, index) {
layer.close(index);
showLogDom.html("");
setMsg(op, text);
});
}
});
function setMsg(opt, args) {
const data = {
op: opt,
scriptId: scriptId,
args: args
};
layer.load(1, {
shade: [0.3, '#fff']
});
ws.send(JSON.stringify(data));
setTimeout(function () {
layer.closeAll('loading');
}, 2000);
}
} else {
showLogDom.html('你的浏览器不支持WebSocket');
}
function setOpBtn(flag) {
if (flag) {
$('.btn-op[op="start"]').show();
$('.btn-op[op="stop"]').hide();
} else {
$('.btn-op[op="start"]').hide();
$('.btn-op[op="stop"]').show();
}
}
function scrollToBotomm() {
var h = showLogDom[0].scrollHeight;
if (window.screen.height * 2 < h) {
showLogDom.html("已清空屏幕缓存" + '<br/>');
showLogDom.scrollTop(0);
return;
}
showLogDom.scrollTop(h);
}
}
</script>
</html>

View File

@ -0,0 +1,103 @@
<!DOCTYPE html>
<html>
<head>
#parse("./common/head.vm")
<title>oss</title>
<style>
body {
padding-top: 20px;
}
</style>
</head>
<body>
<div class="layui-container">
<form action="save.json" class="layui-form">
<div class="layui-form-item">
<label class="layui-form-label">模板ID</label>
<div class="layui-input-block">
<input type="text" name="id" placeholder="请输入模板Id" required lay-verify="required"
value="$!item.id" #if($item)readonly#end class="layui-input">
<input type="hidden" name="type" value="$type">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">模板名称</label>
<div class="layui-input-block">
<input type="text" name="name" placeholder="请输入模板名称" required lay-verify="required"
value="$!item.name" class="layui-input">
</div>
</div>
<div class="layui-form-item">
<label class="layui-form-label">模板内容</label>
<div class="layui-input-block">
<textarea name="context" style="height: 50vh;" placeholder="请输入模板内容"
class="layui-textarea auto">$!item.context</textarea>
</div>
</div>
#if($user.isSystemUser())
<div class="layui-form-item">
<div class="layui-input-block">
<button class="layui-btn" lay-submit lay-filter="formDemo">立即提交</button>
#if($item)
<a class="layui-btn layui-btn-warm" data-id="$item.id" id="delete">删除</a>
#end
</div>
</div>
#end
</form>
</div>
</body>
<script type="text/javascript">
function loadSuccess() {
form.on('submit(formDemo)', function (formData) {
loadingAjax({
url: formData.form.action,
data: formData.field,
success: function (data) {
layer.msg(data.msg);
if (200 == data.code) {
setTimeout(function () {
parent.location.reload();
}, 2000);
}
}
});
return false;
});
$("#delete").click(function () {
var id = $(this).attr("data-id");
var type = $(this).attr("type");
var msg = '确定删除此脚本模板吗 ' + id + '';
layer.confirm(msg, {
title: '系统提示'
}, function (index) {
loadingAjax({
url: './del.json',
data: {
id: id
},
success: function (data) {
layer.msg(data.msg);
if (200 == data.code) {
autoClose();
}
}
});
});
});
}
function autoClose() {
setTimeout(function () {
var index = parent.layer.getFrameIndex(window.name);
parent.layer.close(index);
parent.location.reload();
}, 2000);
}
</script>
</html>

View File

@ -0,0 +1,107 @@
<!DOCTYPE html>
<html>
<head>
#parse("./common/head.vm")
<title>项目管理系统</title>
<style>
body {
padding: 20px;
}
</style>
</head>
<body>
<div class="layui-row">
<button onclick="location.reload();" class="layui-btn layui-btn-sm">刷新表格</button>
#if($user.isSystemUser())
<button onclick="editItem('');" class="layui-btn layui-btn-sm">添加模板</button>
<button id="import" class="layui-btn layui-btn-sm">导入模板</button>
#end
</div>
<div class="layui-form">
<table class="layui-table">
<thead>
<tr>
<th>模板ID</th>
<th>模板名称</th>
<th>修改时间</th>
<th>最后执行人</th>
<th>操作</th>
</tr>
</thead>
<tbody>
#foreach($item in $array)
<tr>
<td>$item.id</td>
<td>$item.name</td>
<td>$item.modifyTime</td>
<td>$item.lastRunUser</td>
<td>
<button onclick="runScript('$item.id')" class="layui-btn layui-btn-warm layui-btn-sm">执行
</button>
<button onclick="editItem('$item.id')" class="layui-btn layui-btn-sm">#if($user.isSystemUser())
修改#else 预览#end
</button>
</td>
</tr>
#end
#if(!$array || $array.size()<=0)
<tr>
<td colspan="5">没有相关信息</td>
</tr>
#end
</tbody>
</table>
</div>
</body>
<script type="text/javascript">
function loadSuccess() {
uploadRender({
elem: '#import',
accept: 'file',
data: {},
multiple: false,
exts: 'bat|sh',
acceptMime: '.bat,.sh',
url: './upload',
before: function () {
layer.load(1, {
shade: [0.5, '#fff']
});
},
done: function (res, index, upload) {
layer.msg(res.msg);
setTimeout(function () {
location.reload();
}, 2000);
},
error: function () {
layer.closeAll();
layer.msg('上传失败');
}
});
}
function editItem(id) {
layerOpen({
type: 2,
title: '管理脚本模板',
shade: 0.8,
area: ['80%', '90%'],
content: 'item.html?id=' + id
});
}
function runScript(id) {
tabChange({
id: "runScript-" + id,
url: './script/console.html?id=' + id,
title: id + '控制台',
});
}
</script>
</html>

View File

@ -5,22 +5,27 @@
#parse("./common/head.vm")
<title>白名单目录</title>
<style>
.div {
margin-top: 20px;
body {
padding: 10px;
}
.auto {
width: 100%;
height: 100%;
}
.edit-div {
width: 80%;
height: 25vh;
}
</style>
</head>
<body>
<div class="layui-container div">
<div class="layui-container">
<form action="" class="layui-form" id="form_user">
<div class="layui-form-item">
<div class="layui-inline" style="width: 80%;height:30vh;">
<div class="layui-inline edit-div">
<label class="layui-form-label">项目路径</label>
<div class="layui-input-block auto">
<textarea name="project" placeholder="请输入项目存放路径白名单,回车支持输入多个路径,系统会自动过滤 ../ 路径、不允许输入根路径"
@ -29,7 +34,7 @@
</div>
</div>
<div class="layui-form-item">
<div class="layui-inline" style="width: 80%;height:30vh;">
<div class="layui-inline edit-div">
<label class="layui-form-label">证书路径</label>
<div class="layui-input-block auto">
<textarea name="certificate" placeholder="请输入证书存放路径白名单,回车支持输入多个路径,系统会自动过滤 ../ 路径、不允许输入根路径"
@ -38,7 +43,7 @@
</div>
</div>
<div class="layui-form-item">
<div class="layui-inline" style="width: 80%;height:30vh;">
<div class="layui-inline edit-div">
<label class="layui-form-label">nginx目录</label>
<div class="layui-input-block auto">
<textarea name="nginx" placeholder="请输入nginx存放路径白名单回车支持输入多个路径系统会自动过滤 ../ 路径、不允许输入根路径"

View File

@ -65,6 +65,19 @@
</select>
</div>
</div>
<div class="layui-inline">
<label class="layui-form-label" style="width: auto !important;">用户</label>
<div class="layui-input-inline">
<select name="selectUser" id="selectUser" lay-verify="required" lay-filter="selectUser"
lay-search="">
<option value="">请选择</option>
#foreach($item in $userArray)
<option value="$item.id">$item.name</option>
#end
<option value="system">系统管理员</option>
</select>
</div>
</div>
</div>
</form>
</script>
@ -107,6 +120,8 @@
renderDate();
// 选中
$("#selectNode option[value='" + apiWhere.selectNode + "']").attr("selected", "selected");
$("#selectUser option[value='" + apiWhere.selectUser + "']").attr("selected", "selected");
form.render();
}
});
@ -141,6 +156,11 @@
return true;
});
form.on('select(selectUser)', function (data) {
apiWhere.selectUser = data.value;
reloadTable();
return true;
});
}
function renderDate() {

View File

@ -1,3 +1,4 @@
import cn.hutool.system.SystemUtil;
import com.sun.tools.attach.AttachNotSupportedException;
import sun.jvmstat.monitor.*;
@ -21,7 +22,7 @@ public class TestJvm {
// Properties properties = virtualMachine.getAgentProperties();
// System.out.println(properties);
// }
System.out.println(SystemUtil.getJavaRuntimeInfo().getVersion());
// 获取监控主机
MonitoredHost local = MonitoredHost.getMonitoredHost("localhost");

View File

@ -1,68 +1,105 @@
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.LineHandler;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.StrUtil;
import cn.keepbx.jpom.BaseJpomApplication;
import java.io.*;
public class TestRun {
public static void main(String[] args) throws IOException, InterruptedException {
testProcessBuilder("javaw -jar D:\\sdfsdf\\test-jar\\springboot-test-jar-0.0.1-SNAPSHOT.jar -Dapplication=test -Dbasedir=D:\\sdfsdf\\test-jar >> D:\\sdfsdf\\test.log");
testProcessBuilder("D:\\jpom\\agent\\script\\test\\script.bat");
}
public static void testProcessBuilder(String command) {
public static void testProcessBuilder(String... command) {
boolean err = false;
try {
ProcessBuilder processBuilder = new ProcessBuilder(command.split(" "));
final int[] count = {0};
ProcessBuilder processBuilder = new ProcessBuilder(command);
//初始化ProcessBuilder对象
// processBuilder.inheritIO()
Process p = processBuilder.start();
//用于存储执行命令的结果
BufferedReader results = new BufferedReader(new InputStreamReader(p.getInputStream()));
String s;
while ((s = results.readLine()) != null) {
System.out.println(s);
}
//用于存储执行命令的错误信息
BufferedReader errors = new BufferedReader(new InputStreamReader(p.getErrorStream()));
while ((s = errors.readLine()) != null) {
System.err.println(s);
err = true;
}
OutputStream outputStream = p.getOutputStream();
InputStream inputStream = p.getInputStream();
ThreadUtil.execute(new Runnable() {
@Override
public void run() {
InputStreamReader inputStreamReader = new InputStreamReader(inputStream, CharsetUtil.CHARSET_GBK);
//用于存储执行命令的结果
BufferedReader results = new BufferedReader(inputStreamReader);
IoUtil.readLines(results, new LineHandler() {
@Override
public void handle(String line) {
// String result = CharsetUtil.convert(line, CharsetUtil.CHARSET_ISO_8859_1, CharsetUtil.CHARSET_GBK);
System.out.println(line);
count[0]++;
if (count[0] == 10) {
processBuilder.inheritIO();
try {
outputStream.write(2);
outputStream.flush();
} catch (IOException e) {
e.printStackTrace();
}
try {
results.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
inputStreamReader.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
outputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println("3*****");
try {
p.waitFor();
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
p.getErrorStream().close();
} catch (IOException e) {
e.printStackTrace();
}
System.out.println(123456);
p.destroy();
}
}
});
System.out.println("123");
//用于存储执行命令的错误信息
BufferedReader errors = new BufferedReader(new InputStreamReader(p.getErrorStream(), CharsetUtil.CHARSET_GBK));
IoUtil.readLines(errors, new LineHandler() {
@Override
public void handle(String line) {
String result = CharsetUtil.convert(line, null, BaseJpomApplication.getCharset());
System.out.println("error:" + result);
}
});
try {
p.waitFor();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
System.out.println("end");
} catch (Exception e) {
e.printStackTrace();
}
if (err) {
throw new RuntimeException("Error executing " + command);
}
}
private static String exec(String[] cmd) throws IOException, InterruptedException {
// DefaultSystemLog.LOG().info(Arrays.toString(cmd));
String result;
Process process;
process = Runtime.getRuntime().exec(cmd[0]);
InputStream is;
process.waitFor();
OutputStream outputStream = process.getOutputStream();
outputStream.write(1);
is = process.getInputStream();
result = IoUtil.read(is, CharsetUtil.CHARSET_UTF_8);
if (StrUtil.isEmpty(result)) {
System.out.println("321");
is = process.getErrorStream();
result = IoUtil.read(is, CharsetUtil.CHARSET_UTF_8);
}
is.close();
process.destroy();
if (StrUtil.isEmpty(result)) {
result = "没有返回任何执行信息";
}
return result;
}
}