Merge remote-tracking branch 'origin/dev' into dev

This commit is contained in:
Hong 2024-01-10 11:01:21 +08:00
commit 5fe3a20d46
791 changed files with 79575 additions and 68800 deletions

View File

@ -20,8 +20,8 @@ stages:
jdkVersion: 8
mavenVersion: 3.6.3
commands:
- curl -LfsSo /opt/node-v16.13.1-linux-x64.tar.gz https://npmmirror.com/mirrors/node/v16.13.1/node-v16.13.1-linux-x64.tar.gz
- tar -zxf /opt/node-v16.13.1-linux-x64.tar.gz -C /opt/ && export PATH=/opt/node-v16.13.1-linux-x64/bin:$PATH
- curl -LfsSo /opt/node-v18.19.0-linux-x64.tar.gz https://npmmirror.com/mirrors/node/v18.19.0/node-v18.19.0-linux-x64.tar.gz
- tar -zxf /opt/node-v18.19.0-linux-x64.tar.gz -C /opt/ && export PATH=/opt/node-v18.19.0-linux-x64/bin:$PATH
- npm config set registry https://registry.npmmirror.com/
- cd web-vue && npm install && npm run build
- cd ..

View File

@ -1,27 +1,179 @@
# 🚀 版本日志
### 2.11.0.0-beta
### 2.11.0.9-beta
### 🐣 新增功能
1. 【all】新增 孤独数据管理(查看孤独数据、修正孤独数据)(感谢[@陈旭](https://gitee.com/chenxu8989) [Gitee issues I8UNXZ](https://gitee.com/dromara/Jpom/issues/I8UNXZ)
### 🐞 解决BUG、优化功能
1. 【server】优化 上传文件前解析文件信息采用全局 loading
2. 【server】优化 构建流程交互(采用步骤条)
3. 【server】修复 部分 icon 未更新、部分弹窗列表数据不能正常显示
4. 【server】修复 docker-compose 容器状态无非正确显示
5. 【agent】修复 低版本项目数据未存储节点ID
------
### 2.11.0.8-beta (2024-01-09)
### 🐣 新增功能
1. 【server】新增 前端 UI 支持配置浅色、深色主题、左边菜单主题
### 🐞 解决BUG、优化功能
1. 【server】修复 容器构建 DSL 未回显任何内容
2. 【server】修复 登录页面禁用验证码失效(感谢@ccx2480
------
### 2.11.0.7-beta (2024-01-08)
### 🐞 解决BUG、优化功能
1. 【server】升级 页面 UI 组件、VUE 版本升级到最新
2. 【server】修复 部分低频功能无法正常使用(项目备份文件管理等)
3. 【server】修复 部分执行异常未输出到操作日志文件中(感谢@闫淼淼)
### ⚠️ 注意
1. 取消全局 loading局部loading
2. 编辑器延迟 1 秒加载(避免样式错乱)
3. 所有快捷复制区域变小为一个点击复制图标
4. 弹窗、抽屉样式变动
5. 取消操作引导(临时)
6. 表格将跟随列内容长度自动拉伸出现横向滚动(不会折叠)
7. 个性化配置取消:【页面自动撑开、滚动条显示、页面导航】
8. 新版本前端 node 版本推荐18.19.0
9. json viewer 还未实现
------
### 2.11.0.6-beta (2024-01-05)
### 🐞 解决BUG、优化功能
1. 【all】优化 日志记录器提升日志记录性能
2. 【server】优化 取消/停止构建采用异常来打断子进程
3. 【server】修复 本地构建无法取消
4. 【server】修复 服务端脚本触发器、节点脚本触发器提示找不到用户(感谢@LYY
------
### 2.11.0.5-beta (2024-01-04)
### 🐣 新增功能
1. 【server】新增 工作空间管理中新增概括总览页面
### 🐞 解决BUG、优化功能
1. 【server】优化 支持批量删除构建信息(感谢@奇奇)
2. 【server】修复 删除项目、删除分发检查关联构建失败问题
3. 【all】优化 关闭 Process 方式
4. 【server】优化 节点方法相关页面问题(感谢[@陈旭](https://gitee.com/chenxu8989) [Gitee issues I8TMDW](https://gitee.com/dromara/Jpom/issues/I8TMDW)
------
### 2.11.0.4-beta (2024-01-03)
### 🐞 解决BUG、优化功能
1. 【server】修复 工作空间菜单配置无法使用(感谢@新)
2. 【server】优化 重新同步节点项目、节点脚本缓存交互
3. 【server】优化 SSH 脚本执行模板独立(`/exec/template.sh` -> `/ssh/template.sh`
4. 【server】优化 服务端脚本支持加载脚本模板来实现自动加载部分环境变量
### ⚠️ 注意
如果您自定义过 SSH 脚本默认那么您需要重新同步一下脚本模板`/exec/template.sh` -> `/ssh/template.sh`
新版本 `/exec/template.sh` 中仅在服务端中生效(本地构建脚本、服务端脚本、本地发布脚本)
------
### 2.11.0.3-beta (2024-01-02)
### 🐞 解决BUG、优化功能
1. 【server】修复 没有对应的工作空间权限
------
### 2.11.0.2-beta (2024-01-02)
### 🐞 解决BUG、优化功能
1. 【all】修复 环境变量为 null 是未忽略
------
### 2.11.0.1-beta (2024-01-02)
### 🐣 新增功能
1. 【all】新增 项目支持软链其他项目(代替项目副本)
### 🐞 解决BUG、优化功能
1. 【server】修复 新版页面漏掉项目复制按钮
2. 【server】优化 逻辑节点中项目数和脚本数仅显示当前工作空间数量
3. 【server】优化 项目编辑和节点分发页面支持快捷配置授权目录
4. 【server】优化 项目编辑支持切换节点(快速同步其他节点项目)
5. 【server】修复 没有工作空间权限时页面循环跳转(感谢[@王先生](https://gitee.com/whz_gmg1) [Gitee issues I8RR01](https://gitee.com/dromara/Jpom/issues/I8RR01)
6. 【all】优化 授权目录判断逻辑
7. 【agent】取消 插件端授权目录关闭包含判断(`jpom.whitelist.check-starts-with`)
8. 【server】优化 触发器清理优化、删除用户主动删除关联触发器
9. 【server】优化 DSL 项目控制台支持快捷编辑节点脚本(查看流程信息)
10. 【server】修复 项目触发器无法调用
### ⚠️ 注意
1. 如果您配置了授权目录但是保存项目报错您可以尝试重新报错一下授权目录来自动修复授权目录配置数据
2. 项目控制台日志默认路径调整为插件端数据目录下`project-log/${projectId}/${projectId}.log`
3. 项目控制台日志备份默认路径调整为插件端数据目录下`project-log/${projectId}/back/${projectId}-xxxxxxx.log`
------
### 2.11.0.0-beta (2023-12-29)
### 🐣 新增功能
1. 【server】新增 节点分发可以指定构建历史产物发布
2. 【server】新增 节点分发可以指定文件中心发布
3. 【server】新增 DSL 项目新增 reload 事件(可以开启文件变动触发)
4. 【server】新增 静态文件授权服务端指定目录到工作空间来管理(分发)(感谢@*斌)
5. 【server】新增 节点分发可以指定静态文件发布
### 🐞 解决BUG、优化功能
1. 【server】修复 项目列表批量操作弹窗定时刷新引起异常(感谢@曾梦想仗剑走天涯)
2. 【all】下架 全面下架项目副本功能(请使用 DSL 模式代替)
3. 【all】下架 全面节点证书管理功能(请使用工作空间证书代替)
4. 【all】下架 全下架节点 NGINX 管理功能(请使用 DSL 模式代替)
4. 【all】下架 全架节点 NGINX 管理功能(请使用 DSL 模式代替)
5. 【server】优化 **节点管理仅保留项目管理、脚本管理、脚本日志(其他功能迁移到机器资产管理)**
6. 【server】修复 项目复制按钮点击无响应
7. 【all】优化 查看插件端和服务端的系统日志 websocket 地址
8. 【server】优化 监控机器系统负载保留2位小数
9. 【server】下架 取消节点管理员权限
10. 【server】修复 文件变动触发器不生效的问题
11. 【all】优化 项目操作接口合并4 合 1
12. 【server】优化 配置授权目录需要使用到绝对路径
### ⚠️ 注意
1. 全面下架项目副本功能(请使用 DSL 模式代替)如果您当前使用到此功能请先手动备份相关数据
2. 升级后项目副本数据会被人工或者系统更新项目数据自动删除(请一定提前做好备份操作)
3. 全面下架节点证书管理功能(请使用工作空间证书代替)如果您当前使用到此功能请先手动备份相关数据
4. 全面下架全下架节点 NGINX 管理功能(请使用 DSL 模式代替)如果您当前使用到此功能请先手动备份相关数据
> 为什么要下架上述功能:由于版本迭代已经有更好的新功能可以代替之前旧功能,并且新功能从另一种角度更方便。下架也是为了我们后续版本维护迭代更高效
>❓ 为什么要下架上述功能:由于版本迭代已经有更好的新功能可以代替之前旧功能,并且新功能从另一种角度更方便。下架也是为了我们后续版本维护迭代更高效
- 【白名单】关键词统一调整为【授权】
- 【黑名单】关键词统一调整为【禁止】
------

View File

@ -6,6 +6,9 @@
<p align="center">
<strong>🚀简而轻的低侵入式在线构建、自动部署、日常运维、项目监控软件</strong>
</p>
<p align="center">
<strong>更是一款原生 ops 软件</strong>
</p>
<p align="center">
<a target="_blank" href="https://gitee.com/dromara/Jpom">
@ -26,13 +29,7 @@
<a target="_blank" href="https://www.codacy.com/gh/dromara/Jpom/dashboard?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=dromara/Jpom&amp;utm_campaign=Badge_Grade">
<img src="https://app.codacy.com/project/badge/Grade/843b953f1446449c9a075e44ea778336" alt="codacy"/>
</a>
<a target="_blank" href="https://jpom.top/pages/praise/join/">
<img src='https://img.shields.io/badge/%E5%BE%AE%E4%BF%A1%E7%BE%A4(%E8%AF%B7%E5%A4%87%E6%B3%A8%3AJpom)-jpom66-yellowgreen.svg' alt='jpom66 请备注jpom'/>
</a>
</p>
<p align="center">
<a target="_blank" href="https://jpom.top/pages/changelog/new/">
<a target="_blank" href="https://jpom.top/pages/changelog/new/">
<img src="https://img.shields.io/github/v/release/dromara/Jpom.svg" alt="docker pull"/>
</a>
<a target="_blank" href="https://hub.docker.com/repository/docker/jpomdocker/jpom">
@ -68,7 +65,7 @@
3. 账号可以开启 **MFA 两步验证**提高账号安全性
- 界面形式实时查看项目运行状态、控制台日志、管理项目文件
1. 在线修改项目文本文件
- Docker 容器管理、Docker swarm 集群管理(**Docker ui**
- Docker 容器管理、Docker Swarm 集群管理(**Docker UI**
- **在线 SSH 终端**,让您在没有 Xshell、FinalShell 等软件也能轻松管理服务器
1. 登录 Jpom 系统后不需要知道服务器密码
2. 能指定 SSH 禁止执行的命令,避免执行高风险命令,并且能自动执行命令日志
@ -88,7 +85,7 @@
- 项目状态监控异常自动报警、自动尝试重启
1. 支持邮件 + 钉钉群 + 微信群通知,主动感知项目运行状况
- 节点脚本模版+定时执行或者触发器,拓展更多功能
- 重要路径白名单模式,杜绝用户误操作系统文件
- 重要路径授权配置,杜绝用户误操作系统文件
### 🔔️ 特别提醒
@ -379,6 +376,26 @@ docker-compose -f docker-compose.yml up
2. 服务端示例:
[`logback.xml`](https://gitee.com/dromara/Jpom/blob/master/modules/server/src/main/resources/config_default/logback.xml)
## 📝 常见问题、操作说明
- [文档主页](https://jpom.top/)
- [FQA](https://jpom.top/pages/FQA/)
- [名词解释](https://jpom.top/pages/FQA/proper-noun/)
### 实践案例
> 里面有部分图片加载可能比较慢
1. [本地构建 + SSH 发布 java 项目](https://jpom.top/pages/practice/build-java-ssh-release/)
2. [本地构建 + 项目发布 node 项目](https://jpom.top/pages/practice/build-node-release/)
3. [本地构建 + SSH 发布 node 项目](https://jpom.top/pages/practice/build-node-ssh-release/)
4. [本地构建 + 自定义管理 python 项目](https://jpom.top/pages/practice/project-dsl-python/)
5. [自定义管理 java 项目](https://jpom.top/pages/practice/project-dsl-java/)
6. [管理编译安装的 nginx](https://jpom.top/pages/practice/node-nginx/)
7. [管理 docker](https://jpom.top/pages/practice/docker-cli/)
8. [容器构建 + 项目发布 java 项目](https://jpom.top/pages/practice/build-docker-java-node-release/)
9. [更新实践案例>>](https://jpom.top/pages/practice/)
## 构建案例仓库代码
1. [Jboot 案例代码](https://gitee.com/keepbx/Jpom-demo-case/tree/master/jboot-test)
@ -400,30 +417,37 @@ yarn --cwd xxxx/ build
mvn -f xxxx/pom.xml clean package
```
## 📝 常见问题、操作说明
- [文档主页](https://jpom.top/)
- [FQA](https://jpom.top/pages/FQA/)
- [名词解释](https://jpom.top/pages/FQA/proper-noun/)
### 实践案例
> 里面有部分图片加载可能比较慢
1. [本地构建 + SSH 发布 java 项目](https://jpom.top/pages/practice/build-java-ssh-release/)
2. [本地构建 + 项目发布 node 项目](https://jpom.top/pages/practice/build-node-release/)
3. [本地构建 + SSH 发布 node 项目](https://jpom.top/pages/practice/build-node-ssh-release/)
4. [本地构建 + 自定义管理 python 项目](https://jpom.top/pages/practice/project-dsl-python/)
5. [自定义管理 java 项目](https://jpom.top/pages/practice/project-dsl-java/)
6. [管理编译安装的 nginx](https://jpom.top/pages/practice/node-nginx/)
7. [管理 docker](https://jpom.top/pages/practice/docker-cli/)
8. [容器构建 + 项目发布 java 项目](https://jpom.top/pages/practice/build-docker-java-node-release/)
9. [更新实践案例>>](https://jpom.top/pages/practice/)
## 🛠️ 整体架构
![jpom-func-arch](https://jpom.top/images/jpom-func-arch.png)
## 🐞 交流讨论 、反馈 BUG、提出建议等
1. 快扫描下方左侧微信群二维码和我们一起交流讨论吧!(添加小助手:备注 Jpom 进群)
2. 开源项目离不开社区的支持,如果项目帮助到了你,并且想给我们加个餐。
欢迎扫描下方[微信、支付宝收款码赞赏](https://jpom.top/images/praise-qrcorde.png)
或通过[码云赞赏](https://gitee.com/dromara/Jpom)
(在项目首页下方点击捐赠,支持微信和支付宝)。[赞赏记录](https://jpom.top/pages/praise/publicity/)
3. 购买开源周边商品:[周边介绍](https://jpom.top/pages/shop/)
4. 企业技术服务请单独与我们联系沟通服务方案
5. 反馈 BUG、提出建议欢迎新建[issues](https://gitee.com/dromara/Jpom/issues),开发人员会不定时查看回复。
6. 参与贡献,请查看[贡献指南](#贡献指南)。
感谢所有赞赏以及参与贡献的小伙伴,你们的支持是我们不断更新前进的动力!
![wx-qrcode-praise.png](https://jpom.top/images/praise-qrcorde.png)
## 💖 周边商品
为了更好地维持开源项目,我们决定推出周边商品。
购买支持我们这样您既获得了一份小商品我们也获得了您购买商品的利润(周边商品的价格会比市场价稍高,介意请勿下单)
<p align="center">
<img src="https://jpom.top/images/gift-shop/shop-home.jpg" style="zoom: 80%;box-shadow: 0px 0px 20px 10px rgba(0,0,0,0.06);" alt="shop home">
</p>
## 🔨贡献指南
### 贡献须知
@ -473,21 +497,6 @@ Jpom 作为开源项目,离不开社区的支持,欢迎任何人修改和提
> 目前用到的主要是 dev 和 docs 分支,接受 PR 修改,其他的分支为归档分支,贡献者可以不用管。
## 🐞 交流讨论 、反馈 BUG、提出建议等
1. 快扫描下方左侧微信群二维码和我们一起交流讨论吧!(添加小助手:备注 Jpom 进群)
2. 开源项目离不开社区的支持,如果项目帮助到了你,并且想给我们加个餐。
欢迎扫描下方[微信、支付宝收款码赞赏](https://jpom.top/images/praise-qrcorde.png)
或通过[码云赞赏](https://gitee.com/dromara/Jpom)
(在项目首页下方点击捐赠,支持微信和支付宝)。[赞赏记录](https://jpom.top/pages/praise/publicity/)
3. 企业技术服务请单独与我们联系沟通服务方案
4. 反馈 BUG、提出建议欢迎新建[issues](https://gitee.com/dromara/Jpom/issues),开发人员会不定时查看回复。
5. 参与贡献,请查看[贡献指南](#贡献指南)。
感谢所有赞赏以及参与贡献的小伙伴,你们的支持是我们不断更新前进的动力!
![wx-qrcode-praise.png](https://jpom.top/images/praise-qrcorde.png)
## 🌍 知识星球
<p align="center">
@ -505,7 +514,7 @@ Jpom 作为开源项目,离不开社区的支持,欢迎任何人修改和提
| hippo4j | [https://gitee.com/magegoofy/hippo4j](https://gitee.com/magegoofy/hippo4j) | 强大的动态线程池框架,附带监控报警功能。 |
| HertzBeat | [https://gitee.com/dromara/hertzbeat](https://gitee.com/dromara/hertzbeat) | 易用友好的云监控系统, 无需 Agent, 强大自定义监控能力。 |
## 鸣谢
## 🤝 鸣谢
- 感谢 JetBrains 提供的免费开源 License

View File

@ -7,6 +7,7 @@ currently being supported with security updates.
| Version | Supported |
|-------------|--------------------|
| 2.11.x | :white_check_mark: |
| 2.10.x | :white_check_mark: |
| 2.9.x | :white_check_mark: |
| 2.8.x | :x: |

View File

@ -1,3 +1,3 @@
JPOM_VERSION=2.10.47.7
JPOM_VERSION=2.11.0.8
# Server Token 生产部署请更换
SERVER_TOKEN=7094f673-2c53-4fc1-82e7-86e528449d97

View File

@ -30,7 +30,7 @@
<parent>
<groupId>org.dromara.jpom.agent-transport</groupId>
<artifactId>jpom-agent-transport-parent</artifactId>
<version>2.10.47</version>
<version>2.11.0.8</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -29,9 +29,9 @@ import lombok.NoArgsConstructor;
* @since 2022/12/24
*/
@NoArgsConstructor
public class AgentException extends RuntimeException {
public class TransportAgentException extends RuntimeException {
public AgentException(String message) {
public TransportAgentException(String message) {
super(message);
}
}

View File

@ -30,7 +30,7 @@
<parent>
<groupId>org.dromara.jpom.agent-transport</groupId>
<artifactId>jpom-agent-transport-parent</artifactId>
<version>2.10.47</version>
<version>2.11.0.8</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -108,7 +108,7 @@ public class HttpTransportServer implements TransportServer {
}
} catch (Exception e) {
log.error("编码异常", e);
throw new AgentException("节点传输信息编码异常:" + e.getMessage());
throw new TransportAgentException("节点传输信息编码异常:" + e.getMessage());
}
});
}
@ -124,7 +124,7 @@ public class HttpTransportServer implements TransportServer {
log.debug("Completed {}", body);
if (status != HttpStatus.HTTP_OK) {
log.warn("{} 响应异常 状态码错误:{} {}", nodeInfo.name(), status, body);
throw new AgentException(nodeInfo.name() + " 节点响应异常,状态码错误:" + status);
throw new TransportAgentException(nodeInfo.name() + " 节点响应异常,状态码错误:" + status);
}
return body;
});

View File

@ -29,7 +29,7 @@
<parent>
<artifactId>jpom-parent</artifactId>
<groupId>org.dromara.jpom</groupId>
<version>2.10.47</version>
<version>2.11.0.8</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<packaging>pom</packaging>
@ -38,7 +38,7 @@
<module>agent-transport-http</module>
</modules>
<modelVersion>4.0.0</modelVersion>
<version>2.10.47</version>
<version>2.11.0.8</version>
<groupId>org.dromara.jpom.agent-transport</groupId>
<artifactId>jpom-agent-transport-parent</artifactId>
<name>Jpom Agent Transport</name>

View File

@ -29,12 +29,12 @@
<parent>
<artifactId>jpom-parent</artifactId>
<groupId>org.dromara.jpom</groupId>
<version>2.10.47</version>
<version>2.11.0.8</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>agent</artifactId>
<version>2.10.47</version>
<version>2.11.0.8</version>
<name>Jpom Agent</name>
<properties>
<start-class>org.dromara.jpom.JpomAgentApplication</start-class>

View File

@ -30,7 +30,7 @@ package org.dromara.jpom.common;
*/
public class AgentConst {
/**
* 白名单文件
* 授权文件
*/
public static final String WHITELIST_DIRECTORY = "whitelistDirectory.json";
/**

View File

@ -23,6 +23,7 @@
package org.dromara.jpom.common;
import lombok.extern.slf4j.Slf4j;
import org.dromara.jpom.exception.BaseExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
/**

View File

@ -28,12 +28,12 @@ import cn.hutool.core.util.URLUtil;
import cn.hutool.extra.servlet.ServletUtil;
import org.dromara.jpom.model.data.NodeProjectInfoModel;
import org.dromara.jpom.service.manage.ProjectInfoService;
import org.springframework.util.Assert;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import java.util.Objects;
/**
* agent
@ -42,6 +42,7 @@ import java.util.Objects;
* @since 2019/4/17
*/
public abstract class BaseAgentController extends BaseJpomController {
@Resource
protected ProjectInfoService projectInfoService;
@ -83,8 +84,9 @@ public abstract class BaseAgentController extends BaseJpomController {
* @return NodeProjectInfoModel
*/
protected NodeProjectInfoModel getProjectInfoModel() {
NodeProjectInfoModel nodeProjectInfoModel = tryGetProjectInfoModel();
Objects.requireNonNull(nodeProjectInfoModel, "获取项目信息失败");
String id = getParameter("id");
NodeProjectInfoModel nodeProjectInfoModel = tryGetProjectInfoModel(id);
Assert.notNull(nodeProjectInfoModel, "获取项目信息失败:" + id);
return nodeProjectInfoModel;
}
@ -95,7 +97,7 @@ public abstract class BaseAgentController extends BaseJpomController {
*/
protected NodeProjectInfoModel getProjectInfoModel(String id) {
NodeProjectInfoModel nodeProjectInfoModel = tryGetProjectInfoModel(id);
Objects.requireNonNull(nodeProjectInfoModel, "获取项目信息失败");
Assert.notNull(nodeProjectInfoModel, "获取项目信息失败:" + id);
return nodeProjectInfoModel;
}
@ -105,10 +107,9 @@ public abstract class BaseAgentController extends BaseJpomController {
}
protected NodeProjectInfoModel tryGetProjectInfoModel(String id) {
NodeProjectInfoModel nodeProjectInfoModel = null;
if (StrUtil.isNotEmpty(id)) {
nodeProjectInfoModel = projectInfoService.getItem(id);
return projectInfoService.getItem(id);
}
return nodeProjectInfoModel;
return null;
}
}

View File

@ -28,8 +28,6 @@ import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import javax.annotation.Resource;
/**
* @author bwcx_jzy
* @since 2022/12/8
@ -37,10 +35,14 @@ import javax.annotation.Resource;
@Configuration
public class WebConfigurer implements WebMvcConfigurer {
@Resource
private ParameterInterceptor parameterInterceptor;
@Resource
private AuthorizeInterceptor authorizeInterceptor;
private final ParameterInterceptor parameterInterceptor;
private final AuthorizeInterceptor authorizeInterceptor;
public WebConfigurer(ParameterInterceptor parameterInterceptor,
AuthorizeInterceptor authorizeInterceptor) {
this.parameterInterceptor = parameterInterceptor;
this.authorizeInterceptor = authorizeInterceptor;
}
@Override

View File

@ -35,31 +35,31 @@ import cn.hutool.core.map.SafeConcurrentHashMap;
import cn.hutool.core.text.StrPool;
import cn.hutool.core.text.StrSplitter;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.system.OsInfo;
import cn.hutool.system.SystemUtil;
import cn.keepbx.jpom.plugins.IPlugin;
import lombok.Lombok;
import lombok.extern.slf4j.Slf4j;
import org.dromara.jpom.common.IllegalArgument2Exception;
import org.dromara.jpom.common.commander.impl.LinuxProjectCommander;
import org.dromara.jpom.common.commander.impl.MacOsProjectCommander;
import org.dromara.jpom.common.commander.impl.WindowsProjectCommander;
import org.dromara.jpom.exception.IllegalArgument2Exception;
import org.dromara.jpom.configuration.ProjectConfig;
import org.dromara.jpom.configuration.ProjectLogConfig;
import org.dromara.jpom.model.RunMode;
import org.dromara.jpom.model.data.DslYmlDto;
import org.dromara.jpom.model.data.NodeProjectInfoModel;
import org.dromara.jpom.model.system.NetstatModel;
import org.dromara.jpom.plugin.PluginFactory;
import org.dromara.jpom.script.DslScriptBuilder;
import org.dromara.jpom.service.manage.ProjectInfoService;
import org.dromara.jpom.service.script.DslScriptServer;
import org.dromara.jpom.socket.AgentFileTailWatcher;
import org.dromara.jpom.system.AgentConfig;
import org.dromara.jpom.system.JpomRuntimeException;
import org.dromara.jpom.socket.ConsoleCommandOp;
import org.dromara.jpom.util.CommandUtil;
import org.dromara.jpom.util.JvmUtil;
import org.dromara.jpom.webhook.DefaultWebhookPluginImpl;
import org.springframework.util.Assert;
import java.io.File;
import java.io.IOException;
import java.nio.charset.Charset;
import java.util.*;
import java.util.concurrent.TimeUnit;
@ -67,6 +67,7 @@ import java.util.function.BiFunction;
import java.util.jar.Attributes;
import java.util.jar.JarFile;
import java.util.jar.Manifest;
import java.util.stream.Collectors;
/**
* 项目命令执行基类
@ -74,50 +75,39 @@ import java.util.jar.Manifest;
* @author Administrator
*/
@Slf4j
public abstract class AbstractProjectCommander {
public abstract class AbstractProjectCommander implements ProjectCommander {
public static final String RUNNING_TAG = "running";
public static final String STOP_TAG = "stopped";
private static final long BACK_LOG_MIN_SIZE = DataSizeUtil.parse("100KB");
private static AbstractProjectCommander abstractProjectCommander = null;
/**
* 进程Id 获取端口号
*/
public static final Map<Integer, CacheObject<String>> PID_PORT = new SafeConcurrentHashMap<>();
private final Charset fileCharset;
protected final Charset fileCharset;
protected final SystemCommander systemCommander;
protected final ProjectConfig projectConfig;
protected final ProjectLogConfig projectLogConfig;
protected final DslScriptServer dslScriptServer;
protected final ProjectInfoService projectInfoService;
public AbstractProjectCommander(Charset fileCharset) {
public AbstractProjectCommander(Charset fileCharset,
SystemCommander systemCommander,
ProjectConfig projectConfig,
DslScriptServer dslScriptServer,
ProjectInfoService projectInfoService) {
this.fileCharset = fileCharset;
this.systemCommander = systemCommander;
this.projectConfig = projectConfig;
this.projectLogConfig = projectConfig.getLog();
this.dslScriptServer = dslScriptServer;
this.projectInfoService = projectInfoService;
}
/**
* 实例化Commander
*
* @return 命令执行对象
*/
public static AbstractProjectCommander getInstance() {
if (abstractProjectCommander != null) {
return abstractProjectCommander;
}
AgentConfig agentConfig = SpringUtil.getBean(AgentConfig.class);
AgentConfig.ProjectConfig.LogConfig logConfig = agentConfig.getProject().getLog();
OsInfo osInfo = SystemUtil.getOsInfo();
if (osInfo.isLinux()) {
// Linux系统
abstractProjectCommander = new LinuxProjectCommander(logConfig.getFileCharset());
} else if (osInfo.isWindows()) {
// Windows系统
abstractProjectCommander = new WindowsProjectCommander(logConfig.getFileCharset());
} else if (osInfo.isMac()) {
abstractProjectCommander = new MacOsProjectCommander(logConfig.getFileCharset());
} else {
throw new JpomRuntimeException("不支持的:" + osInfo.getName());
}
return abstractProjectCommander;
}
//---------------------------------------------------- 基本操作----start
@ -127,7 +117,15 @@ public abstract class AbstractProjectCommander {
* @param nodeProjectInfoModel 项目
* @return null 是条件不足
*/
public abstract String buildJavaCommand(NodeProjectInfoModel nodeProjectInfoModel);
public abstract String buildRunCommand(NodeProjectInfoModel nodeProjectInfoModel);
/**
* 生成可以执行的命令
*
* @param nodeProjectInfoModel 项目
* @return null 是条件不足
*/
public abstract String buildRunCommand(NodeProjectInfoModel nodeProjectInfoModel, NodeProjectInfoModel originalModel);
protected String getRunJavaPath(NodeProjectInfoModel nodeProjectInfoModel, boolean w) {
// if (StrUtil.isEmpty(nodeProjectInfoModel.getJdkId())) {
@ -144,37 +142,25 @@ public abstract class AbstractProjectCommander {
// return jdkJavaPath;
}
/**
* 启动
*
* @param nodeProjectInfoModel 项目
* @return 结果
* @throws Exception 异常
*/
public CommandOpResult start(NodeProjectInfoModel nodeProjectInfoModel) throws Exception {
return this.start(nodeProjectInfoModel, false);
}
/**
* 启动
*
* @param nodeProjectInfoModel 项目
* @param sync dsl 是否同步执行
* @return 结果
* @throws Exception 异常
*/
public CommandOpResult start(NodeProjectInfoModel nodeProjectInfoModel, boolean sync) throws Exception {
String msg = checkStart(nodeProjectInfoModel);
protected CommandOpResult start(NodeProjectInfoModel nodeProjectInfoModel, NodeProjectInfoModel originalModel, boolean sync) {
String msg = this.checkStart(nodeProjectInfoModel, originalModel);
if (msg != null) {
return CommandOpResult.of(false, msg);
}
RunMode runMode = nodeProjectInfoModel.getRunMode();
RunMode runMode = originalModel.getRunMode();
if (runMode == RunMode.Dsl) {
//
this.runDsl(nodeProjectInfoModel, "start", (baseProcess, action) -> {
String log = nodeProjectInfoModel.getAbsoluteLog();
this.runDsl(originalModel, ConsoleCommandOp.start.name(), (baseProcess, action) -> {
String log = projectInfoService.resolveAbsoluteLog(nodeProjectInfoModel, originalModel);
try {
DslScriptBuilder.run(baseProcess, nodeProjectInfoModel, action, log, sync);
dslScriptServer.run(baseProcess, nodeProjectInfoModel, originalModel, action, log, sync);
} catch (Exception e) {
throw Lombok.sneakyThrow(e);
}
@ -182,18 +168,18 @@ public abstract class AbstractProjectCommander {
});
} else {
String command = this.buildJavaCommand(nodeProjectInfoModel);
String command = this.buildRunCommand(nodeProjectInfoModel, originalModel);
if (command == null) {
return CommandOpResult.of(false, "没有需要执行的命令");
}
// 执行命令
ThreadUtil.execute(() -> {
try {
File file = FileUtil.file(nodeProjectInfoModel.allLib());
File file = projectInfoService.resolveLibFile(nodeProjectInfoModel);
if (SystemUtil.getOsInfo().isWindows()) {
CommandUtil.execSystemCommand(command, file);
} else {
CommandUtil.asyncExeLocalCommand(file, command);
CommandUtil.asyncExeLocalCommand(command, file);
}
} catch (Exception e) {
log.error("执行命令失败", e);
@ -201,9 +187,9 @@ public abstract class AbstractProjectCommander {
});
}
//
this.loopCheckRun(nodeProjectInfoModel, true);
CommandOpResult status = this.status(nodeProjectInfoModel);
this.asyncWebHooks(nodeProjectInfoModel, "start", "result", status.msgStr());
this.loopCheckRun(nodeProjectInfoModel, originalModel, true);
CommandOpResult status = this.status(nodeProjectInfoModel, originalModel);
this.asyncWebHooks(nodeProjectInfoModel, originalModel, "start", "result", status.msgStr());
return status;
}
@ -219,18 +205,8 @@ public abstract class AbstractProjectCommander {
* @param listening 是否只获取检查状态的
* @return 数组
*/
public abstract List<NetstatModel> listNetstat(int pid, boolean listening);
protected abstract List<NetstatModel> listNetstat(int pid, boolean listening);
/**
* 停止
*
* @param nodeProjectInfoModel 项目
* @return 结果
* @throws Exception 异常
*/
public CommandOpResult stop(NodeProjectInfoModel nodeProjectInfoModel) throws Exception {
return this.stop(nodeProjectInfoModel, false);
}
/**
* 停止
@ -238,39 +214,41 @@ public abstract class AbstractProjectCommander {
* @param nodeProjectInfoModel 项目
* @param sync dsl 是否同步执行
* @return 结果
* @throws Exception 异常
*/
public CommandOpResult stop(NodeProjectInfoModel nodeProjectInfoModel, boolean sync) throws Exception {
protected CommandOpResult stop(NodeProjectInfoModel nodeProjectInfoModel, NodeProjectInfoModel originalModel, boolean sync) {
RunMode runMode = nodeProjectInfoModel.getRunMode();
if (runMode == RunMode.File) {
return CommandOpResult.of(true, "file 类型项目没有 stop");
}
Tuple tuple = this.stopBefore(nodeProjectInfoModel);
Tuple tuple = this.stopBefore(nodeProjectInfoModel, originalModel);
CommandOpResult status = tuple.get(1);
String webHook = tuple.get(0);
if (status.isSuccess()) {
// 运行中
if (runMode == RunMode.Dsl) {
//
this.runDsl(nodeProjectInfoModel, "stop", (process, action) -> {
String log = nodeProjectInfoModel.getAbsoluteLog();
this.runDsl(originalModel, ConsoleCommandOp.stop.name(), (process, action) -> {
String log = projectInfoService.resolveAbsoluteLog(nodeProjectInfoModel, originalModel);
try {
DslScriptBuilder.run(process, nodeProjectInfoModel, action, log, sync);
dslScriptServer.run(process, nodeProjectInfoModel, originalModel, action, log, sync);
} catch (Exception e) {
throw Lombok.sneakyThrow(e);
}
return null;
});
boolean checkRun = this.loopCheckRun(nodeProjectInfoModel, false);
boolean checkRun = this.loopCheckRun(nodeProjectInfoModel, originalModel, false);
return CommandOpResult.of(checkRun, checkRun ? "stop done" : "stop done,but unsuccessful")
.appendMsg(status.getMsgs())
.appendMsg(webHook);
} else {
//
return this.stopJava(nodeProjectInfoModel, status.getPid()).appendMsg(status.getMsgs()).appendMsg(webHook);
return this.stopJava(nodeProjectInfoModel, originalModel, status.getPid()).appendMsg(status.getMsgs()).appendMsg(webHook);
}
}
return CommandOpResult.of(true).appendMsg(status.getMsgs()).appendMsg(webHook);
return CommandOpResult.of(true).
appendMsg(status.getMsgs()).
appendMsg(webHook);
}
/**
@ -279,26 +257,24 @@ public abstract class AbstractProjectCommander {
* @param nodeProjectInfoModel 项目
* @param pid 进程ID
* @return 结果
* @throws Exception 异常
*/
public abstract CommandOpResult stopJava(NodeProjectInfoModel nodeProjectInfoModel, int pid) throws Exception;
protected abstract CommandOpResult stopJava(NodeProjectInfoModel nodeProjectInfoModel, NodeProjectInfoModel originalModel, int pid);
/**
* 停止之前
*
* @param nodeProjectInfoModel 项目
* @return 结果
* @throws Exception 异常
*/
private Tuple stopBefore(NodeProjectInfoModel nodeProjectInfoModel) throws Exception {
String beforeStop = this.webHooks(nodeProjectInfoModel, "beforeStop");
private Tuple stopBefore(NodeProjectInfoModel nodeProjectInfoModel, NodeProjectInfoModel originalModel) {
String beforeStop = this.webHooks(nodeProjectInfoModel.token(), nodeProjectInfoModel, "beforeStop");
// 再次查看进程信息
CommandOpResult result = this.status(nodeProjectInfoModel);
CommandOpResult result = this.status(nodeProjectInfoModel, originalModel);
if (result.isSuccess()) {
// 端口号缓存
PID_PORT.remove(result.getPid());
}
this.asyncWebHooks(nodeProjectInfoModel, "stop", "result", result);
this.asyncWebHooks(nodeProjectInfoModel, originalModel, "stop", "result", result);
return new Tuple(StrUtil.emptyToDefault(beforeStop, StrUtil.EMPTY), result);
}
@ -312,17 +288,54 @@ public abstract class AbstractProjectCommander {
public void asyncWebHooks(NodeProjectInfoModel nodeProjectInfoModel,
String type, Object... other) {
String token = nodeProjectInfoModel.getToken();
Opt.ofBlankAble(token)
NodeProjectInfoModel infoModel = projectInfoService.resolveModel(nodeProjectInfoModel);
this.asyncWebHooks(nodeProjectInfoModel, infoModel, type, other);
}
/**
* 执行 webhooks 通知
*
* @param nodeProjectInfoModel 项目信息
* @param type 类型
* @param other 其他参数
*/
public void asyncWebHooks(NodeProjectInfoModel nodeProjectInfoModel,
NodeProjectInfoModel originalModel,
String type, Object... other) {
// webhook 通知
Opt.ofBlankAble(nodeProjectInfoModel.token())
.ifPresent(s ->
ThreadUtil.execute(() -> {
try {
this.webHooks(nodeProjectInfoModel, type, other);
String result = this.webHooks(s, nodeProjectInfoModel, type, other);
Optional.ofNullable(result).ifPresent(s1 -> log.debug("[{}]-{}触发器结果:{}", nodeProjectInfoModel.getId(), type, s1));
} catch (Exception e) {
log.error("project webhook", e);
}
})
);
// 判断文件变动
if (StrUtil.equals(type, "fileChange")) {
RunMode runMode = originalModel.getRunMode();
if (runMode == RunMode.Dsl) {
DslYmlDto dslYmlDto = originalModel.mustDslConfig();
if (dslYmlDto.hasRunProcess(ConsoleCommandOp.reload.name())) {
DslYmlDto.Run run = dslYmlDto.getRun();
Boolean fileChangeReload = run.getFileChangeReload();
if (fileChangeReload != null && fileChangeReload) {
// 需要执行重载事件
ThreadUtil.execute(() -> {
try {
CommandOpResult reload = this.reload(nodeProjectInfoModel, originalModel);
log.info("触发项目 reload 事件:{}", reload);
} catch (Exception e) {
log.error("重载项目异常", e);
}
});
}
}
}
}
}
/**
@ -333,21 +346,26 @@ public abstract class AbstractProjectCommander {
* @param other 其他参数
* @return 结果
*/
private String webHooks(NodeProjectInfoModel nodeProjectInfoModel,
String type, Object... other) throws Exception {
String token = nodeProjectInfoModel.getToken();
private String webHooks(String token, NodeProjectInfoModel nodeProjectInfoModel, String type, Object... other) {
if (StrUtil.isEmpty(token)) {
return null;
}
IPlugin plugin = PluginFactory.getPlugin("webhook");
Map<String, Object> map = new HashMap<>(10);
map.put("projectId", nodeProjectInfoModel.getId());
map.put("projectName", nodeProjectInfoModel.getName());
map.put("type", type);
map.put("JPOM_WEBHOOK_EVENT", DefaultWebhookPluginImpl.WebhookEvent.PROJECT);
for (int i = 0; i < other.length; i += 2) {
map.put(other[i].toString(), other[i + 1]);
}
Object execute = plugin.execute(token, map);
return Convert.toStr(execute, StrUtil.EMPTY);
try {
Object execute = plugin.execute(token, map);
return Convert.toStr(execute, StrUtil.EMPTY);
} catch (Exception e) {
throw Lombok.sneakyThrow(e);
}
}
/**
@ -355,40 +373,42 @@ public abstract class AbstractProjectCommander {
*
* @param nodeProjectInfoModel 项目
* @return 结果
* @throws Exception 异常
*/
public CommandOpResult restart(NodeProjectInfoModel nodeProjectInfoModel) throws Exception {
RunMode runMode = nodeProjectInfoModel.getRunMode();
protected CommandOpResult restart(NodeProjectInfoModel nodeProjectInfoModel, NodeProjectInfoModel originalModel) {
RunMode runMode = originalModel.getRunMode();
if (runMode == RunMode.File) {
return CommandOpResult.of(true, "file 类型项目没有 restart");
}
this.asyncWebHooks(nodeProjectInfoModel, "beforeRestart");
this.asyncWebHooks(nodeProjectInfoModel, originalModel, "beforeRestart");
if (runMode == RunMode.Dsl) {
DslYmlDto.BaseProcess dslProcess = nodeProjectInfoModel.tryDslProcess("restart");
DslYmlDto.BaseProcess dslProcess = originalModel.tryDslProcess(ConsoleCommandOp.restart.name());
if (dslProcess != null) {
// 如果存在自定义 restart 流程
//
this.runDsl(nodeProjectInfoModel, "restart", (process, action) -> {
String log = nodeProjectInfoModel.getAbsoluteLog();
this.runDsl(originalModel, ConsoleCommandOp.restart.name(), (process, action) -> {
String log = projectInfoService.resolveAbsoluteLog(nodeProjectInfoModel, originalModel);
try {
DslScriptBuilder.run(process, nodeProjectInfoModel, action, log, false);
dslScriptServer.run(process, nodeProjectInfoModel, originalModel, action, log, false);
} catch (Exception e) {
throw Lombok.sneakyThrow(e);
}
return null;
});
// 等待 状态成功
boolean run = this.loopCheckRun(nodeProjectInfoModel, true);
return CommandOpResult.of(run, run ? "restart done" : "restart done,but unsuccessful");
boolean run = this.loopCheckRun(nodeProjectInfoModel, originalModel, true);
CommandOpResult result = CommandOpResult.of(run, run ? "restart done" : "restart done,but unsuccessful");
this.asyncWebHooks(nodeProjectInfoModel, originalModel, "restart", "result", result);
return result;
//return new Tuple(run ? "restart done,but unsuccessful" : "restart done", resultMsg);
}
}
boolean run = this.isRun(nodeProjectInfoModel);
boolean run = this.isRun(nodeProjectInfoModel, originalModel);
CommandOpResult stopMsg = null;
if (run) {
stopMsg = this.stop(nodeProjectInfoModel, true);
stopMsg = this.stop(nodeProjectInfoModel, originalModel, true);
}
CommandOpResult startMsg = this.start(nodeProjectInfoModel);
CommandOpResult startMsg = this.start(nodeProjectInfoModel, originalModel, false);
if (stopMsg != null) {
startMsg.appendMsg(stopMsg.getMsgs());
}
@ -400,35 +420,38 @@ public abstract class AbstractProjectCommander {
*
* @param nodeProjectInfoModel 项目
* @return null 检查一切正常
* @throws Exception 异常
*/
private String checkStart(NodeProjectInfoModel nodeProjectInfoModel) throws Exception {
CommandOpResult status = this.status(nodeProjectInfoModel);
private String checkStart(NodeProjectInfoModel nodeProjectInfoModel, NodeProjectInfoModel originalModel) {
CommandOpResult status = this.status(nodeProjectInfoModel, originalModel);
if (status.isSuccess()) {
return "当前程序正在运行中,不能重复启动,PID:" + status.getPid();
}
String lib = nodeProjectInfoModel.allLib();
File fileLib = new File(lib);
File fileLib = projectInfoService.resolveLibFile(nodeProjectInfoModel);
File[] files = fileLib.listFiles();
if (files == null || files.length == 0) {
return "项目目录没有任何文件,请先到项目文件管理中上传文件";
}
//
RunMode runMode = nodeProjectInfoModel.getRunMode();
if (runMode == RunMode.Dsl) {
RunMode checkRunMode = originalModel.getRunMode();
if (checkRunMode == RunMode.Dsl) {
//
String dslContent = nodeProjectInfoModel.getDslContent();
} else if (runMode == RunMode.ClassPath || runMode == RunMode.JavaExtDirsCp) {
originalModel.mustDslConfig();
} else if (checkRunMode == RunMode.ClassPath || checkRunMode == RunMode.JavaExtDirsCp) {
// 判断主类
String mainClass = originalModel.mainClass();
try (JarClassLoader jarClassLoader = JarClassLoader.load(fileLib)) {
jarClassLoader.loadClass(nodeProjectInfoModel.getMainClass());
jarClassLoader.loadClass(mainClass);
} catch (ClassNotFoundException notFound) {
return "没有找到对应的MainClass:" + nodeProjectInfoModel.getMainClass();
return "没有找到对应的MainClass:" + mainClass;
} catch (IOException io) {
throw Lombok.sneakyThrow(io);
}
} else if (runMode == RunMode.Jar || runMode == RunMode.JarWar) {
List<File> fileList = NodeProjectInfoModel.listJars(nodeProjectInfoModel);
} else if (checkRunMode == RunMode.Jar || checkRunMode == RunMode.JarWar) {
List<File> fileList = this.listJars(checkRunMode, fileLib);
if (fileList.isEmpty()) {
return String.format("一级目录没有%s包,请先到文件管理中上传程序的%s", runMode.name(), runMode.name());
return String.format("一级目录没有%s包,请先到文件管理中上传程序的%s", checkRunMode.name(), checkRunMode.name());
}
File jarFile = fileList.get(0);
String checkJar = checkJar(jarFile);
@ -439,10 +462,16 @@ public abstract class AbstractProjectCommander {
return "当前项目类型不支持启动";
}
// 备份日志
backLog(nodeProjectInfoModel);
this.backLog(nodeProjectInfoModel, originalModel);
return null;
}
/**
* 校验jar包
*
* @param jarFile jar 文件
* @return mainClass
*/
private static String checkJar(File jarFile) {
try (JarFile jarFile1 = new JarFile(jarFile)) {
Manifest manifest = jarFile1.getManifest();
@ -469,13 +498,11 @@ public abstract class AbstractProjectCommander {
* @param nodeProjectInfoModel 项目实体
* @return true 开启日志备份
*/
private boolean resolveOpenLogBack(NodeProjectInfoModel nodeProjectInfoModel) {
RunMode runMode = nodeProjectInfoModel.getRunMode();
AgentConfig agentConfig = SpringUtil.getBean(AgentConfig.class);
boolean autoBackToFile = agentConfig.getProject().getLog().isAutoBackupToFile();
private boolean resolveOpenLogBack(NodeProjectInfoModel nodeProjectInfoModel, NodeProjectInfoModel originalModel) {
RunMode runMode = originalModel.getRunMode();
boolean autoBackToFile = projectLogConfig.isAutoBackupToFile();
if (runMode == RunMode.Dsl) {
DslYmlDto dslYmlDto = nodeProjectInfoModel.dslConfig();
return Optional.ofNullable(dslYmlDto)
return Optional.ofNullable(originalModel.dslConfig())
.map(DslYmlDto::getConfig)
.map(DslYmlDto.Config::getAutoBackToFile)
.orElse(autoBackToFile);
@ -490,7 +517,18 @@ public abstract class AbstractProjectCommander {
* @return 结果
*/
public String backLog(NodeProjectInfoModel nodeProjectInfoModel) {
File file = new File(nodeProjectInfoModel.getLog());
NodeProjectInfoModel infoModel = projectInfoService.resolveModel(nodeProjectInfoModel);
return this.backLog(nodeProjectInfoModel, infoModel);
}
/**
* 清空日志信息
*
* @param nodeProjectInfoModel 项目
* @return 结果
*/
public String backLog(NodeProjectInfoModel nodeProjectInfoModel, NodeProjectInfoModel originalModel) {
File file = projectInfoService.resolveAbsoluteLogFile(nodeProjectInfoModel, originalModel);
if (!file.exists() || file.isDirectory()) {
return "not exists";
}
@ -499,15 +537,16 @@ public abstract class AbstractProjectCommander {
if (file.length() <= BACK_LOG_MIN_SIZE) {
return "ok";
}
boolean openLogBack = this.resolveOpenLogBack(nodeProjectInfoModel);
boolean openLogBack = this.resolveOpenLogBack(nodeProjectInfoModel, originalModel);
if (openLogBack) {
// 开启日志备份才移动文件
File backPath = nodeProjectInfoModel.getLogBack();
backPath = new File(backPath, DateTime.now().toString(DatePattern.PURE_DATETIME_FORMAT) + ".log");
File backPath = projectInfoService.resolveLogBack(nodeProjectInfoModel, originalModel);
String pathId = DateTime.now().toString(DatePattern.PURE_DATETIME_FORMAT) + ".log";
backPath = new File(backPath, pathId);
FileUtil.copy(file, backPath, true);
}
// 清空日志
String r = AbstractSystemCommander.getInstance().emptyLogFile(file);
String r = systemCommander.emptyLogFile(file);
if (StrUtil.isNotEmpty(r)) {
log.info(r);
}
@ -522,16 +561,28 @@ public abstract class AbstractProjectCommander {
* @param nodeProjectInfoModel 项目
* @return 状态
*/
public CommandOpResult status(NodeProjectInfoModel nodeProjectInfoModel) {
RunMode runMode = nodeProjectInfoModel.getRunMode();
protected CommandOpResult status(NodeProjectInfoModel nodeProjectInfoModel) {
NodeProjectInfoModel originalModel = projectInfoService.resolveModel(nodeProjectInfoModel);
return this.status(nodeProjectInfoModel, originalModel);
}
/**
* 查询项目状态
*
* @param nodeProjectInfoModel 项目
* @return 状态
*/
protected CommandOpResult status(NodeProjectInfoModel nodeProjectInfoModel, NodeProjectInfoModel originalModel) {
RunMode runMode = originalModel.getRunMode();
if (runMode == RunMode.File) {
return CommandOpResult.of(false, "file 类型项目没有运行状态");
}
if (runMode == RunMode.Dsl) {
List<String> status = this.runDsl(nodeProjectInfoModel, "status", (baseProcess, action) -> {
List<String> status = this.runDsl(originalModel, ConsoleCommandOp.status.name(), (baseProcess, action) -> {
// 提前判断脚本 id,避免填写错误在删除项目检测状态时候异常
try {
return DslScriptBuilder.syncRun(baseProcess, nodeProjectInfoModel, action);
Tuple tuple = dslScriptServer.syncRun(baseProcess, nodeProjectInfoModel, originalModel, action);
return tuple.get(1);
} catch (IllegalArgument2Exception argument2Exception) {
log.warn("执行 DSL 脚本异常:{}", argument2Exception.getMessage());
return CollUtil.newArrayList(argument2Exception.getMessage());
@ -540,8 +591,8 @@ public abstract class AbstractProjectCommander {
return Optional.ofNullable(status)
.map(strings -> {
String log = nodeProjectInfoModel.getAbsoluteLog();
FileUtil.appendLines(strings, FileUtil.file(log), fileCharset);
File log = projectInfoService.resolveAbsoluteLogFile(nodeProjectInfoModel, originalModel);
FileUtil.appendLines(strings, log, fileCharset);
return strings;
})
.map(CollUtil::getLast)
@ -562,6 +613,36 @@ public abstract class AbstractProjectCommander {
}
}
/**
* 重新加载
*
* @param nodeProjectInfoModel 项目
* @return 结果
*/
protected CommandOpResult reload(NodeProjectInfoModel nodeProjectInfoModel, NodeProjectInfoModel originalModel) {
RunMode runMode = originalModel.getRunMode();
Assert.state(runMode == RunMode.Dsl, "非 DSL 项目不支持此操作");
CommandOpResult commandOpResult = this.runDsl(originalModel, ConsoleCommandOp.reload.name(), (baseProcess, action) -> {
// 提前判断脚本 id,避免填写错误在删除项目检测状态时候异常
try {
Tuple tuple = dslScriptServer.syncRun(baseProcess, nodeProjectInfoModel, originalModel, action);
int code = tuple.get(0);
List<String> list = tuple.get(1);
// 如果退出码为 0 认为执行成功
return CommandOpResult.of(code == 0, list);
} catch (IllegalArgument2Exception argument2Exception) {
log.warn("执行 DSL 脚本异常:{}", argument2Exception.getMessage());
return CommandOpResult.of(false, argument2Exception.getMessage());
}
});
// 缓存执行结果
NodeProjectInfoModel update = new NodeProjectInfoModel();
update.setLastReloadResult(commandOpResult);
projectInfoService.updateById(update, nodeProjectInfoModel.getId());
this.asyncWebHooks(nodeProjectInfoModel, originalModel, "reload", "result", commandOpResult);
return commandOpResult;
}
/**
* 查看状态
*
@ -570,12 +651,23 @@ public abstract class AbstractProjectCommander {
*/
protected String status(String tag) {
String jpsStatus = this.getJpsStatus(tag);
if (StrUtil.equals(AbstractProjectCommander.STOP_TAG, jpsStatus) && SystemUtil.getOsInfo().isLinux()) {
return getLinuxPsStatus(tag);
if (StrUtil.equals(AbstractProjectCommander.STOP_TAG, jpsStatus)) {
// 通过系统命令查询
return this.bySystemPs(tag);
}
return jpsStatus;
}
/**
* 通过系统命令查询进程是否存在
*
* @param tag 进程标识
* @return 是否存在
*/
protected String bySystemPs(String tag) {
return AbstractProjectCommander.STOP_TAG;
}
/**
* 尝试jps 中查看进程id
*
@ -590,27 +682,7 @@ public abstract class AbstractProjectCommander {
return StrUtil.format("{}:{}", AbstractProjectCommander.RUNNING_TAG, pid);
}
/**
* 尝试ps -ef | grep 中查看进程id
*
* @param tag 进程标识
* @return 运行标识
*/
private String getLinuxPsStatus(String tag) {
String execSystemCommand = CommandUtil.execSystemCommand("ps -ef | grep " + tag);
log.debug("getLinuxPsStatus {} {}", tag, execSystemCommand);
List<String> list = StrSplitter.splitTrim(execSystemCommand, StrUtil.LF, true);
for (String item : list) {
if (JvmUtil.checkCommandLineIsJpom(item, tag)) {
String[] split = StrUtil.splitToArray(item, StrUtil.SPACE);
return StrUtil.format("{}:{}", AbstractProjectCommander.RUNNING_TAG, split[1]);
}
}
return AbstractProjectCommander.STOP_TAG;
}
//---------------------------------------------------- 基本操作----end
//---------------------------------------------------- 基本操作----end
/**
* 获取进程占用的主要端口
@ -618,15 +690,15 @@ public abstract class AbstractProjectCommander {
* @param pid 进程id
* @return 端口
*/
public String getMainPort(int pid) {
if (pid <= 0) {
public String getMainPort(Integer pid) {
if (pid == null || pid <= 0) {
return StrUtil.DASHED;
}
String cachePort = CacheObject.get(PID_PORT, pid);
if (cachePort != null) {
return cachePort;
}
List<NetstatModel> list = listNetstat(pid, true);
List<NetstatModel> list = this.listNetstat(pid, true);
if (list == null) {
return StrUtil.DASHED;
}
@ -683,7 +755,18 @@ public abstract class AbstractProjectCommander {
* @return true 正在运行
*/
public boolean isRun(NodeProjectInfoModel nodeProjectInfoModel) {
CommandOpResult result = this.status(nodeProjectInfoModel);
NodeProjectInfoModel originalModel = projectInfoService.resolveModel(nodeProjectInfoModel);
return this.isRun(nodeProjectInfoModel, originalModel);
}
/**
* 是否正在运行
*
* @param nodeProjectInfoModel 项目
* @return true 正在运行
*/
public boolean isRun(NodeProjectInfoModel nodeProjectInfoModel, NodeProjectInfoModel originalModel) {
CommandOpResult result = this.status(nodeProjectInfoModel, originalModel);
return result.isSuccess();
}
@ -694,9 +777,9 @@ public abstract class AbstractProjectCommander {
*
* @return 和参数status相反
*/
protected boolean loopCheckRun(NodeProjectInfoModel nodeProjectInfoModel, boolean status) {
int statusWaitTime = AgentConfig.ProjectConfig.getInstance().getStatusWaitTime();
return this.loopCheckRun(nodeProjectInfoModel, statusWaitTime, status);
protected boolean loopCheckRun(NodeProjectInfoModel nodeProjectInfoModel, NodeProjectInfoModel originalModel, boolean status) {
int statusWaitTime = projectConfig.getStatusWaitTime();
return this.loopCheckRun(nodeProjectInfoModel, originalModel, statusWaitTime, status);
}
/***
@ -707,14 +790,14 @@ public abstract class AbstractProjectCommander {
*
* @return 如果和期望一致则返回 true反之 false
*/
protected boolean loopCheckRun(NodeProjectInfoModel nodeProjectInfoModel, int waitTime, boolean status) {
protected boolean loopCheckRun(NodeProjectInfoModel nodeProjectInfoModel, NodeProjectInfoModel originalModel, int waitTime, boolean status) {
waitTime = Math.max(waitTime, 1);
int statusDetectionInterval = AgentConfig.ProjectConfig.getInstance().getStatusDetectionInterval();
int statusDetectionInterval = projectConfig.getStatusDetectionInterval();
statusDetectionInterval = Math.max(statusDetectionInterval, 1);
int loopCount = (int) (TimeUnit.SECONDS.toMillis(waitTime) / 500);
int count = 0;
do {
if (this.isRun(nodeProjectInfoModel) == status) {
if (this.isRun(nodeProjectInfoModel, originalModel) == status) {
// 是期望的结果
return true;
}
@ -722,4 +805,124 @@ public abstract class AbstractProjectCommander {
} while (count++ < loopCount);
return false;
}
/**
* 执行shell命令
*
* @param consoleCommandOp 执行的操作
* @param nodeProjectInfoModel 项目信息
* @return 执行结果
*/
public CommandOpResult execCommand(ConsoleCommandOp consoleCommandOp, NodeProjectInfoModel nodeProjectInfoModel) {
NodeProjectInfoModel originalModel = projectInfoService.resolveModel(nodeProjectInfoModel);
CommandOpResult result;
// 执行命令
switch (consoleCommandOp) {
case restart:
result = this.restart(nodeProjectInfoModel, originalModel);
break;
case start:
result = this.start(nodeProjectInfoModel, originalModel, false);
break;
case stop:
result = this.stop(nodeProjectInfoModel, originalModel, false);
break;
case status: {
result = this.status(nodeProjectInfoModel, originalModel);
break;
}
case reload: {
result = this.reload(nodeProjectInfoModel, originalModel);
break;
}
case showlog:
default:
throw new IllegalArgumentException(consoleCommandOp + " error");
}
return result;
}
/**
* 获取项目文件中的所有jar 文件
*
* @param runMode 运行模式
* @param path 目录
* @return list
*/
protected List<File> listJars(RunMode runMode, String path) {
//File fileLib = projectInfoService.resolveLibFile(nodeProjectInfoModel);
return this.listJars(runMode, FileUtil.file(path));
}
/**
* 获取项目文件中的所有jar 文件
*
* @param runMode 运行模式
* @param path 目录
* @return list
*/
protected List<File> listJars(RunMode runMode, File path) {
File[] files = path.listFiles();
if (files == null) {
return new ArrayList<>();
}
return Arrays.stream(files)
.filter(File::isFile)
.filter(file -> {
if (runMode == RunMode.ClassPath || runMode == RunMode.Jar || runMode == RunMode.JavaExtDirsCp) {
return StrUtil.endWith(file.getName(), FileUtil.JAR_FILE_EXT, true);
} else if (runMode == RunMode.JarWar) {
return StrUtil.endWith(file.getName(), "war", true);
}
return false;
})
.collect(Collectors.toList());
}
/**
* 拼接java 执行的jar路径
*
* @param nodeProjectInfoModel 项目
* @return classpath 或者 jar
*/
protected String getClassPathLib(NodeProjectInfoModel nodeProjectInfoModel, String lib) {
RunMode runMode = nodeProjectInfoModel.getRunMode();
List<File> files = this.listJars(runMode, lib);
if (CollUtil.isEmpty(files)) {
return "";
}
// 获取lib下面的所有jar包
StringBuilder classPath = new StringBuilder();
int len = files.size();
if (runMode == RunMode.ClassPath) {
classPath.append("-classpath ");
} else if (runMode == RunMode.Jar || runMode == RunMode.JarWar) {
classPath.append("-jar ");
// 只取一个jar文件
len = 1;
} else if (runMode == RunMode.JavaExtDirsCp) {
classPath.append("-Djava.ext.dirs=");
String javaExtDirsCp = nodeProjectInfoModel.javaExtDirsCp();
String[] split = StrUtil.splitToArray(javaExtDirsCp, StrUtil.COLON);
if (ArrayUtil.isEmpty(split)) {
classPath.append(". -cp ");
} else {
classPath.append(split[0]).append(" -cp ");
if (split.length > 1) {
classPath.append(split[1]).append(FileUtil.PATH_SEPARATOR);
}
}
} else {
return StrUtil.EMPTY;
}
for (int i = 0; i < len; i++) {
File file = files.get(i);
classPath.append(file.getAbsolutePath());
if (i != len - 1) {
classPath.append(FileUtil.PATH_SEPARATOR);
}
}
return classPath.toString();
}
}

View File

@ -22,11 +22,6 @@
*/
package org.dromara.jpom.common.commander;
import cn.hutool.system.SystemUtil;
import org.dromara.jpom.common.commander.impl.LinuxSystemCommander;
import org.dromara.jpom.common.commander.impl.MacOsSystemCommander;
import org.dromara.jpom.common.commander.impl.WindowsSystemCommander;
import org.dromara.jpom.system.JpomRuntimeException;
import org.dromara.jpom.util.CommandUtil;
import java.io.File;
@ -37,27 +32,8 @@ import java.io.File;
* @author bwcx_jzy
* @since 2019/4/16
*/
public abstract class AbstractSystemCommander {
public abstract class AbstractSystemCommander implements SystemCommander {
private static AbstractSystemCommander abstractSystemCommander = null;
public static AbstractSystemCommander getInstance() {
if (abstractSystemCommander != null) {
return abstractSystemCommander;
}
if (SystemUtil.getOsInfo().isLinux()) {
// Linux系统
abstractSystemCommander = new LinuxSystemCommander();
} else if (SystemUtil.getOsInfo().isWindows()) {
// Windows系统
abstractSystemCommander = new WindowsSystemCommander();
} else if (SystemUtil.getOsInfo().isMac()) {
abstractSystemCommander = new MacOsSystemCommander();
} else {
throw new JpomRuntimeException("不支持的:" + SystemUtil.getOsInfo().getName());
}
return abstractSystemCommander;
}
/**
* 清空文件内容
@ -67,29 +43,29 @@ public abstract class AbstractSystemCommander {
*/
public abstract String emptyLogFile(File file);
/**
* 查询服务状态
*
* @param serviceName 服务名称
* @return true 运行中
*/
public abstract boolean getServiceStatus(String serviceName);
/**
* 启动服务
*
* @param serviceName 服务名称
* @return 结果
*/
public abstract String startService(String serviceName);
/**
* 关闭服务
*
* @param serviceName 服务名称
* @return 结果
*/
public abstract String stopService(String serviceName);
// /**
// * 查询服务状态
// *
// * @param serviceName 服务名称
// * @return true 运行中
// */
// public abstract boolean getServiceStatus(String serviceName);
//
// /**
// * 启动服务
// *
// * @param serviceName 服务名称
// * @return 结果
// */
// public abstract String startService(String serviceName);
//
// /**
// * 关闭服务
// *
// * @param serviceName 服务名称
// * @return 结果
// */
// public abstract String stopService(String serviceName);
/**
* 构建kill 命令

View File

@ -22,9 +22,14 @@
*/
package org.dromara.jpom.common.commander;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.text.StrSplitter;
import cn.hutool.core.util.StrUtil;
import lombok.Lombok;
import lombok.extern.slf4j.Slf4j;
import org.dromara.jpom.configuration.ProjectConfig;
import org.dromara.jpom.model.data.NodeProjectInfoModel;
import org.dromara.jpom.service.manage.ProjectInfoService;
import org.dromara.jpom.service.script.DslScriptServer;
import org.dromara.jpom.util.CommandUtil;
import org.dromara.jpom.util.JvmUtil;
@ -40,46 +45,63 @@ import java.util.Optional;
* @author bwcx_jzy
* @since 2021/12/17
*/
@Slf4j
public abstract class BaseUnixProjectCommander extends AbstractProjectCommander {
public BaseUnixProjectCommander(Charset fileCharset) {
super(fileCharset);
public BaseUnixProjectCommander(Charset fileCharset,
SystemCommander systemCommander,
ProjectConfig projectConfig,
DslScriptServer dslScriptServer,
ProjectInfoService projectInfoService) {
super(fileCharset, systemCommander, projectConfig, dslScriptServer, projectInfoService);
}
@Override
public String buildJavaCommand(NodeProjectInfoModel nodeProjectInfoModel) {
String path = NodeProjectInfoModel.getClassPathLib(nodeProjectInfoModel);
public String buildRunCommand(NodeProjectInfoModel nodeProjectInfoModel) {
NodeProjectInfoModel infoModel = projectInfoService.resolveModel(nodeProjectInfoModel);
return this.buildRunCommand(nodeProjectInfoModel, infoModel);
}
@Override
public String buildRunCommand(NodeProjectInfoModel nodeProjectInfoModel, NodeProjectInfoModel originalModel) {
String lib = projectInfoService.resolveLibPath(originalModel);
String path = this.getClassPathLib(originalModel, lib);
if (StrUtil.isBlank(path)) {
return null;
}
String tag = nodeProjectInfoModel.getId();
String absoluteLog = projectInfoService.resolveAbsoluteLog(nodeProjectInfoModel, originalModel);
return StrUtil.format("nohup {} {} {} {} {} {} >> {} 2>&1 &",
getRunJavaPath(nodeProjectInfoModel, false),
Optional.ofNullable(nodeProjectInfoModel.getJvm()).orElse(StrUtil.EMPTY),
JvmUtil.getJpomPidTag(tag, nodeProjectInfoModel.allLib()),
JvmUtil.getJpomPidTag(tag, lib),
path,
Optional.ofNullable(nodeProjectInfoModel.getMainClass()).orElse(StrUtil.EMPTY),
Optional.ofNullable(originalModel.mainClass()).orElse(StrUtil.EMPTY),
Optional.ofNullable(nodeProjectInfoModel.getArgs()).orElse(StrUtil.EMPTY),
nodeProjectInfoModel.getAbsoluteLog());
absoluteLog);
}
@Override
public CommandOpResult stopJava(NodeProjectInfoModel nodeProjectInfoModel, int pid) throws Exception {
File file = FileUtil.file(nodeProjectInfoModel.allLib());
public CommandOpResult stopJava(NodeProjectInfoModel nodeProjectInfoModel, NodeProjectInfoModel originalModel, int pid) {
File file = projectInfoService.resolveLibFile(originalModel);
List<String> result = new ArrayList<>();
boolean success = false;
String kill = AbstractSystemCommander.getInstance().kill(file, pid);
String kill = systemCommander.kill(file, pid);
result.add(kill);
if (this.loopCheckRun(nodeProjectInfoModel, false)) {
if (this.loopCheckRun(nodeProjectInfoModel, originalModel, false)) {
success = true;
} else {
// 强制杀进程
result.add("Kill not completed, test kill -9");
String cmd = String.format("kill -9 %s", pid);
CommandUtil.asyncExeLocalCommand(file, cmd);
try {
CommandUtil.asyncExeLocalCommand(cmd, file);
} catch (Exception e) {
throw Lombok.sneakyThrow(e);
}
//
if (this.loopCheckRun(nodeProjectInfoModel, 5, false)) {
if (this.loopCheckRun(nodeProjectInfoModel, originalModel, 5, false)) {
success = true;
} else {
result.add("Kill -9 not completed, kill -9 failed ");
@ -89,4 +111,24 @@ public abstract class BaseUnixProjectCommander extends AbstractProjectCommander
return CommandOpResult.of(success, status(tag)).appendMsg(result);
// return status(tag) + StrUtil.SPACE + kill;
}
/**
* 尝试ps -ef | grep 中查看进程id
*
* @param tag 进程标识
* @return 运行标识
*/
@Override
protected String bySystemPs(String tag) {
String execSystemCommand = CommandUtil.execSystemCommand("ps -ef | grep " + tag);
log.debug("getPsStatus {} {}", tag, execSystemCommand);
List<String> list = StrSplitter.splitTrim(execSystemCommand, StrUtil.LF, true);
for (String item : list) {
if (JvmUtil.checkCommandLineIsJpom(item, tag)) {
String[] split = StrUtil.splitToArray(item, StrUtil.SPACE);
return StrUtil.format("{}:{}", AbstractProjectCommander.RUNNING_TAG, split[1]);
}
}
return AbstractProjectCommander.STOP_TAG;
}
}

View File

@ -26,10 +26,11 @@ import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import lombok.Getter;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
/**
* 命令操作执行结果
@ -37,17 +38,17 @@ import java.util.List;
* @author bwcx_jzy
* @since 2022/11/30
*/
@Getter
@Data
public class CommandOpResult {
/**
* 是否成功
*/
private boolean success;
private Boolean success;
/**
* 进程id
*/
private int pid;
private Integer pid;
/**
* 多个进程 id
*/
@ -65,6 +66,21 @@ public class CommandOpResult {
*/
private final List<String> msgs = new ArrayList<>();
/**
* 执行是否成功
*
* @return true 成功
*/
public boolean isSuccess() {
return success != null && success;
}
/**
* 构建结构对象
*
* @param msg 结果消息
* @return result
*/
public static CommandOpResult of(String msg) {
int[] pidsArray = null;
String ports = null;
@ -88,7 +104,7 @@ public class CommandOpResult {
}
public static CommandOpResult of(boolean success) {
return of(success, null);
return of(success, (List<String>) null);
}
public static CommandOpResult of(boolean success, String msg) {
@ -98,6 +114,13 @@ public class CommandOpResult {
return commandOpResult;
}
public static CommandOpResult of(boolean success, List<String> msg) {
CommandOpResult commandOpResult = new CommandOpResult();
commandOpResult.success = success;
Optional.ofNullable(msg).ifPresent(commandOpResult.msgs::addAll);
return commandOpResult;
}
public CommandOpResult appendMsg(String msg) {
if (StrUtil.isEmpty(msg)) {
return this;
@ -113,13 +136,6 @@ public class CommandOpResult {
return this;
}
public CommandOpResult appendMsg(String... msgs) {
for (String msg : msgs) {
this.appendMsg(msg);
}
return this;
}
public String msgStr() {
return CollUtil.join(msgs, StrUtil.COMMA);
}

View File

@ -0,0 +1,80 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2019 Code Technology Studio
*
* 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:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* 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.
*/
package org.dromara.jpom.common.commander;
import cn.hutool.system.OsInfo;
import cn.hutool.system.SystemUtil;
import lombok.extern.slf4j.Slf4j;
import org.dromara.jpom.system.JpomRuntimeException;
import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.type.AnnotatedTypeMetadata;
/**
* @author bwcx_jzy
* @since 23/12/29 029
*/
@Configuration
@Slf4j
public class Commander {
public Commander() {
OsInfo osInfo = SystemUtil.getOsInfo();
if (osInfo.isLinux()) {
// Linux系统
log.debug("当前系统为linux");
} else if (osInfo.isWindows()) {
// Windows系统
log.debug("当前系统为windows");
} else if (osInfo.isMac()) {
log.debug("当前系统为mac");
} else {
throw new JpomRuntimeException("不支持的:" + osInfo.getName());
}
}
public static class Windows implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return SystemUtil.getOsInfo().isWindows();
}
}
public static class Linux implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return SystemUtil.getOsInfo().isLinux();
}
}
public static class Mac implements Condition {
@Override
public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
return SystemUtil.getOsInfo().isMac();
}
}
}

View File

@ -0,0 +1,87 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2019 Code Technology Studio
*
* 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:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* 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.
*/
package org.dromara.jpom.common.commander;
import org.dromara.jpom.model.data.NodeProjectInfoModel;
import org.dromara.jpom.socket.ConsoleCommandOp;
/**
* @author bwcx_jzy
* @since 23/12/29 029
*/
public interface ProjectCommander {
/**
* 生成可以执行的命令
*
* @param nodeProjectInfoModel 项目
* @return null 是条件不足
*/
String buildRunCommand(NodeProjectInfoModel nodeProjectInfoModel);
/**
* 执行 webhooks 通知
*
* @param nodeProjectInfoModel 项目信息
* @param type 类型
* @param other 其他参数
*/
void asyncWebHooks(NodeProjectInfoModel nodeProjectInfoModel, String type, Object... other);
/**
* 清空日志信息
*
* @param nodeProjectInfoModel 项目
* @return 结果
*/
String backLog(NodeProjectInfoModel nodeProjectInfoModel);
/**
* 获取进程占用的主要端口
*
* @param pid 进程id
* @return 端口
*/
String getMainPort(Integer pid);
/**
* 是否正在运行
*
* @param nodeProjectInfoModel 项目
* @return true 正在运行
*/
boolean isRun(NodeProjectInfoModel nodeProjectInfoModel);
/**
* 执行shell命令
*
* @param consoleCommandOp 执行的操作
* @param nodeProjectInfoModel 项目信息
* @return 执行结果
*/
CommandOpResult execCommand(ConsoleCommandOp consoleCommandOp, NodeProjectInfoModel nodeProjectInfoModel);
}

View File

@ -20,34 +20,32 @@
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package org.dromara.jpom.system;
package org.dromara.jpom.common.commander;
import lombok.Data;
import org.dromara.jpom.util.BaseFileTailWatcher;
import java.io.File;
/**
* @author bwcx_jzy
* @since 2022/12/17
* @since 23/12/29 029
*/
@Data
public abstract class BaseExtConfig {
/**
* 数据目录
*/
private String path;
public interface SystemCommander {
/**
* 初始读取日志文件行号
* 清空文件内容
*
* @param file 文件
* @return 执行结果
*/
private int initReadLine = 10;
String emptyLogFile(File file);
public void setInitReadLine(int initReadLine) {
this.initReadLine = initReadLine;
BaseFileTailWatcher.setInitReadLine(this.getInitReadLine());
}
public void setPath(String path) {
this.path = path;
ExtConfigBean.setPath(path);
}
/**
* kill 进程
*
* @param pid 进程编号
* @param file 指定文件夹执行
* @return 结束进程命令
*/
String kill(File file, int pid);
}

View File

@ -25,11 +25,19 @@ package org.dromara.jpom.common.commander.impl;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.StrSplitter;
import cn.hutool.core.util.StrUtil;
import lombok.extern.slf4j.Slf4j;
import org.dromara.jpom.common.commander.BaseUnixProjectCommander;
import org.dromara.jpom.common.commander.Commander;
import org.dromara.jpom.common.commander.SystemCommander;
import org.dromara.jpom.configuration.AgentConfig;
import org.dromara.jpom.model.system.NetstatModel;
import org.dromara.jpom.service.manage.ProjectInfoService;
import org.dromara.jpom.service.script.DslScriptServer;
import org.dromara.jpom.util.CommandUtil;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;
import java.nio.charset.Charset;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
@ -39,10 +47,17 @@ import java.util.stream.Collectors;
*
* @author Administrator
*/
@Conditional(Commander.Linux.class)
@Service
@Primary
@Slf4j
public class LinuxProjectCommander extends BaseUnixProjectCommander {
public LinuxProjectCommander(Charset fileCharset) {
super(fileCharset);
public LinuxProjectCommander(AgentConfig agentConfig,
SystemCommander systemCommander,
DslScriptServer dslScriptServer,
ProjectInfoService projectInfoService) {
super(agentConfig.getProject().getLog().getFileCharset(), systemCommander, agentConfig.getProject(), dslScriptServer, projectInfoService);
}
@Override

View File

@ -22,22 +22,25 @@
*/
package org.dromara.jpom.common.commander.impl;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.system.SystemUtil;
import lombok.extern.slf4j.Slf4j;
import org.dromara.jpom.common.commander.AbstractSystemCommander;
import org.dromara.jpom.common.commander.Commander;
import org.dromara.jpom.util.CommandUtil;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Service;
import java.io.File;
import java.util.List;
/**
* @author bwcx_jzy
* @since 2019/4/16
*/
@Slf4j
@Conditional(Commander.Linux.class)
@Service
@Primary
public class LinuxSystemCommander extends AbstractSystemCommander {
@ -47,55 +50,55 @@ public class LinuxSystemCommander extends AbstractSystemCommander {
}
@Override
public boolean getServiceStatus(String serviceName) {
if (StrUtil.startWith(serviceName, StrUtil.SLASH)) {
String ps = getPs(serviceName);
return StrUtil.isNotEmpty(ps);
}
String format = StrUtil.format("service {} status", serviceName);
String result = CommandUtil.execSystemCommand(format);
return StrUtil.containsIgnoreCase(result, "RUNNING");
}
@Override
public String startService(String serviceName) {
if (StrUtil.startWith(serviceName, StrUtil.SLASH)) {
try {
CommandUtil.asyncExeLocalCommand(FileUtil.file(SystemUtil.getUserInfo().getHomeDir()), serviceName);
return "ok";
} catch (Exception e) {
log.error("执行异常", e);
return "执行异常:" + e.getMessage();
}
}
String format = StrUtil.format("service {} start", serviceName);
return CommandUtil.execSystemCommand(format);
}
@Override
public String stopService(String serviceName) {
if (StrUtil.startWith(serviceName, StrUtil.SLASH)) {
String ps = getPs(serviceName);
List<String> list = StrUtil.splitTrim(ps, StrUtil.LF);
if (list == null || list.isEmpty()) {
return "stop";
}
String s = list.get(0);
list = StrUtil.splitTrim(s, StrUtil.SPACE);
if (list == null || list.size() < 2) {
return "stop";
}
File file = new File(SystemUtil.getUserInfo().getHomeDir());
int pid = Convert.toInt(list.get(1), 0);
if (pid <= 0) {
return "error stop";
}
return kill(file, pid);
}
String format = StrUtil.format("service {} stop", serviceName);
return CommandUtil.execSystemCommand(format);
}
// @Override
// public boolean getServiceStatus(String serviceName) {
// if (StrUtil.startWith(serviceName, StrUtil.SLASH)) {
// String ps = getPs(serviceName);
// return StrUtil.isNotEmpty(ps);
// }
// String format = StrUtil.format("service {} status", serviceName);
// String result = CommandUtil.execSystemCommand(format);
// return StrUtil.containsIgnoreCase(result, "RUNNING");
// }
//
// @Override
// public String startService(String serviceName) {
// if (StrUtil.startWith(serviceName, StrUtil.SLASH)) {
// try {
// CommandUtil.asyncExeLocalCommand(FileUtil.file(SystemUtil.getUserInfo().getHomeDir()), serviceName);
// return "ok";
// } catch (Exception e) {
// log.error("执行异常", e);
// return "执行异常:" + e.getMessage();
// }
// }
// String format = StrUtil.format("service {} start", serviceName);
// return CommandUtil.execSystemCommand(format);
// }
//
// @Override
// public String stopService(String serviceName) {
// if (StrUtil.startWith(serviceName, StrUtil.SLASH)) {
// String ps = getPs(serviceName);
// List<String> list = StrUtil.splitTrim(ps, StrUtil.LF);
// if (list == null || list.isEmpty()) {
// return "stop";
// }
// String s = list.get(0);
// list = StrUtil.splitTrim(s, StrUtil.SPACE);
// if (list == null || list.size() < 2) {
// return "stop";
// }
// File file = new File(SystemUtil.getUserInfo().getHomeDir());
// int pid = Convert.toInt(list.get(1), 0);
// if (pid <= 0) {
// return "error stop";
// }
// return kill(file, pid);
// }
// String format = StrUtil.format("service {} stop", serviceName);
// return CommandUtil.execSystemCommand(format);
// }
@Override
public String buildKill(int pid) {

View File

@ -26,10 +26,16 @@ import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.StrSplitter;
import cn.hutool.core.util.StrUtil;
import org.dromara.jpom.common.commander.BaseUnixProjectCommander;
import org.dromara.jpom.common.commander.Commander;
import org.dromara.jpom.common.commander.SystemCommander;
import org.dromara.jpom.configuration.AgentConfig;
import org.dromara.jpom.model.system.NetstatModel;
import org.dromara.jpom.service.manage.ProjectInfoService;
import org.dromara.jpom.service.script.DslScriptServer;
import org.dromara.jpom.util.CommandUtil;
import org.springframework.context.annotation.Conditional;
import org.springframework.stereotype.Service;
import java.nio.charset.Charset;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
@ -41,10 +47,15 @@ import java.util.stream.Collectors;
*
* @author Hotstrip
*/
@Conditional(Commander.Mac.class)
@Service
public class MacOsProjectCommander extends BaseUnixProjectCommander {
public MacOsProjectCommander(Charset fileCharset) {
super(fileCharset);
public MacOsProjectCommander(AgentConfig agentConfig,
SystemCommander systemCommander,
DslScriptServer dslScriptServer,
ProjectInfoService projectInfoService) {
super(agentConfig.getProject().getLog().getFileCharset(), systemCommander, agentConfig.getProject(), dslScriptServer, projectInfoService);
}
@Override
@ -64,26 +75,29 @@ public class MacOsProjectCommander extends BaseUnixProjectCommander {
if (CollUtil.isEmpty(netList)) {
return null;
}
return netList.stream().map(str -> {
List<String> list = StrSplitter.splitTrim(str, " ", true);
if (list.size() < 10) {
return null;
}
NetstatModel netstatModel = new NetstatModel();
netstatModel.setProtocol(list.get(7));
//netstatModel.setReceive(list.get(1));
//netstatModel.setSend(list.get(2));
netstatModel.setLocal(list.get(8));
netstatModel.setForeign(list.get(4));
if ("tcp".equalsIgnoreCase(netstatModel.getProtocol())) {
netstatModel.setStatus(CollUtil.get(list, 9));
netstatModel.setName(CollUtil.get(list, 0));
} else {
netstatModel.setStatus(StrUtil.DASHED);
netstatModel.setName(CollUtil.get(list, 5));
}
return netList.stream()
.map(str -> {
List<String> list = StrSplitter.splitTrim(str, " ", true);
if (list.size() < 10) {
return null;
}
NetstatModel netstatModel = new NetstatModel();
netstatModel.setProtocol(list.get(7));
//netstatModel.setReceive(list.get(1));
//netstatModel.setSend(list.get(2));
netstatModel.setLocal(list.get(8));
netstatModel.setForeign(list.get(4));
if ("tcp".equalsIgnoreCase(netstatModel.getProtocol())) {
netstatModel.setStatus(CollUtil.get(list, 9));
netstatModel.setName(CollUtil.get(list, 0));
} else {
netstatModel.setStatus(StrUtil.DASHED);
netstatModel.setName(CollUtil.get(list, 5));
}
return netstatModel;
}).filter(Objects::nonNull).collect(Collectors.toList());
return netstatModel;
})
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
}

View File

@ -22,21 +22,21 @@
*/
package org.dromara.jpom.common.commander.impl;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.system.SystemUtil;
import lombok.extern.slf4j.Slf4j;
import org.dromara.jpom.common.commander.AbstractSystemCommander;
import org.dromara.jpom.common.commander.Commander;
import org.dromara.jpom.util.CommandUtil;
import org.springframework.context.annotation.Conditional;
import org.springframework.stereotype.Service;
import java.io.File;
import java.util.List;
/**
* @author User
*/
@Slf4j
@Conditional(Commander.Mac.class)
@Service
public class MacOsSystemCommander extends AbstractSystemCommander {
@ -46,72 +46,72 @@ public class MacOsSystemCommander extends AbstractSystemCommander {
}
@Override
public boolean getServiceStatus(String serviceName) {
if (StrUtil.startWith(serviceName, StrUtil.SLASH)) {
String ps = getPs(serviceName);
return StrUtil.isNotEmpty(ps);
}
/**
* Mac OS 里面查询服务的命令是 launchctl list | grep serverName
* 第一个数字是进程的 PID如果进程正在运行如果它不在运行则显示 "-"
* 第二个数字是进程的退出代码如果已完成如果为负则为终止信号的数量
* 第三列进程名称
*/
String format = StrUtil.format("service {} status", serviceName);
String result = CommandUtil.execSystemCommand(format);
return StrUtil.containsIgnoreCase(result, "RUNNING");
}
private String getPs(final String serviceName) {
String ps = StrUtil.format(" ps -ef |grep -w {} | grep -v grep", serviceName);
return CommandUtil.execSystemCommand(ps);
}
@Override
public String startService(String serviceName) {
if (StrUtil.startWith(serviceName, StrUtil.SLASH)) {
try {
CommandUtil.asyncExeLocalCommand(FileUtil.file(SystemUtil.getUserInfo().getHomeDir()), serviceName);
return "ok";
} catch (Exception e) {
log.error("执行异常", e);
return "执行异常:" + e.getMessage();
}
}
/**
* Mac OS 里面启动服务命令是 launchctl start serverName
*/
String format = StrUtil.format("service {} start", serviceName);
return CommandUtil.execSystemCommand(format);
}
@Override
public String stopService(String serviceName) {
if (StrUtil.startWith(serviceName, StrUtil.SLASH)) {
String ps = getPs(serviceName);
List<String> list = StrUtil.splitTrim(ps, StrUtil.LF);
if (list == null || list.isEmpty()) {
return "stop";
}
String s = list.get(0);
list = StrUtil.splitTrim(s, StrUtil.SPACE);
if (list == null || list.size() < 2) {
return "stop";
}
File file = new File(SystemUtil.getUserInfo().getHomeDir());
int pid = Convert.toInt(list.get(1), 0);
if (pid <= 0) {
return "error stop";
}
return kill(file, pid);
}
/**
* Mac OS 里面启动服务命令是 launchctl stop serverName
*/
String format = StrUtil.format("service {} stop", serviceName);
return CommandUtil.execSystemCommand(format);
}
// @Override
// public boolean getServiceStatus(String serviceName) {
// if (StrUtil.startWith(serviceName, StrUtil.SLASH)) {
// String ps = getPs(serviceName);
// return StrUtil.isNotEmpty(ps);
// }
// /**
// * Mac OS 里面查询服务的命令是 launchctl list | grep serverName
// * 第一个数字是进程的 PID如果进程正在运行如果它不在运行则显示 "-"
// * 第二个数字是进程的退出代码如果已完成如果为负则为终止信号的数量
// * 第三列进程名称
// */
// String format = StrUtil.format("service {} status", serviceName);
// String result = CommandUtil.execSystemCommand(format);
// return StrUtil.containsIgnoreCase(result, "RUNNING");
// }
//
// private String getPs(final String serviceName) {
// String ps = StrUtil.format(" ps -ef |grep -w {} | grep -v grep", serviceName);
// return CommandUtil.execSystemCommand(ps);
// }
//
// @Override
// public String startService(String serviceName) {
// if (StrUtil.startWith(serviceName, StrUtil.SLASH)) {
// try {
// CommandUtil.asyncExeLocalCommand(FileUtil.file(SystemUtil.getUserInfo().getHomeDir()), serviceName);
// return "ok";
// } catch (Exception e) {
// log.error("执行异常", e);
// return "执行异常:" + e.getMessage();
// }
// }
// /**
// * Mac OS 里面启动服务命令是 launchctl start serverName
// */
// String format = StrUtil.format("service {} start", serviceName);
// return CommandUtil.execSystemCommand(format);
// }
//
// @Override
// public String stopService(String serviceName) {
// if (StrUtil.startWith(serviceName, StrUtil.SLASH)) {
// String ps = getPs(serviceName);
// List<String> list = StrUtil.splitTrim(ps, StrUtil.LF);
// if (list == null || list.isEmpty()) {
// return "stop";
// }
// String s = list.get(0);
// list = StrUtil.splitTrim(s, StrUtil.SPACE);
// if (list == null || list.size() < 2) {
// return "stop";
// }
// File file = new File(SystemUtil.getUserInfo().getHomeDir());
// int pid = Convert.toInt(list.get(1), 0);
// if (pid <= 0) {
// return "error stop";
// }
// return kill(file, pid);
// }
// /**
// * Mac OS 里面启动服务命令是 launchctl stop serverName
// */
// String format = StrUtil.format("service {} stop", serviceName);
// return CommandUtil.execSystemCommand(format);
// }
@Override
public String buildKill(int pid) {

View File

@ -22,18 +22,23 @@
*/
package org.dromara.jpom.common.commander.impl;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.text.StrSplitter;
import cn.hutool.core.util.StrUtil;
import org.dromara.jpom.common.commander.AbstractProjectCommander;
import org.dromara.jpom.common.commander.AbstractSystemCommander;
import org.dromara.jpom.common.commander.CommandOpResult;
import org.dromara.jpom.common.commander.Commander;
import org.dromara.jpom.common.commander.SystemCommander;
import org.dromara.jpom.configuration.AgentConfig;
import org.dromara.jpom.model.data.NodeProjectInfoModel;
import org.dromara.jpom.model.system.NetstatModel;
import org.dromara.jpom.service.manage.ProjectInfoService;
import org.dromara.jpom.service.script.DslScriptServer;
import org.dromara.jpom.util.CommandUtil;
import org.dromara.jpom.util.JvmUtil;
import org.springframework.context.annotation.Conditional;
import org.springframework.stereotype.Service;
import java.nio.charset.Charset;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@ -43,43 +48,57 @@ import java.util.Optional;
*
* @author Administrator
*/
@Conditional(Commander.Windows.class)
@Service
public class WindowsProjectCommander extends AbstractProjectCommander {
public WindowsProjectCommander(Charset fileCharset) {
super(fileCharset);
public WindowsProjectCommander(AgentConfig agentConfig,
SystemCommander systemCommander,
DslScriptServer dslScriptServer,
ProjectInfoService projectInfoService) {
super(agentConfig.getProject().getLog().getFileCharset(), systemCommander, agentConfig.getProject(), dslScriptServer, projectInfoService);
}
@Override
public String buildJavaCommand(NodeProjectInfoModel nodeProjectInfoModel) {
String classPath = NodeProjectInfoModel.getClassPathLib(nodeProjectInfoModel);
public String buildRunCommand(NodeProjectInfoModel nodeProjectInfoModel) {
NodeProjectInfoModel infoModel = projectInfoService.resolveModel(nodeProjectInfoModel);
return this.buildRunCommand(nodeProjectInfoModel, infoModel);
}
@Override
public String buildRunCommand(NodeProjectInfoModel nodeProjectInfoModel, NodeProjectInfoModel originalModel) {
String lib = projectInfoService.resolveLibPath(originalModel);
String classPath = this.getClassPathLib(originalModel, lib);
if (StrUtil.isBlank(classPath)) {
return null;
}
// 拼接命令
String jvm = nodeProjectInfoModel.getJvm();
String tag = nodeProjectInfoModel.getId();
String mainClass = nodeProjectInfoModel.getMainClass();
String mainClass = originalModel.mainClass();
String args = nodeProjectInfoModel.getArgs();
String absoluteLog = projectInfoService.resolveAbsoluteLog(nodeProjectInfoModel, originalModel);
return StrUtil.format("{} {} {} {} {} {} >> {} &",
getRunJavaPath(nodeProjectInfoModel, true),
Optional.ofNullable(jvm).orElse(StrUtil.EMPTY),
JvmUtil.getJpomPidTag(tag, nodeProjectInfoModel.allLib()),
JvmUtil.getJpomPidTag(tag, lib),
classPath,
Optional.ofNullable(mainClass).orElse(StrUtil.EMPTY),
Optional.ofNullable(args).orElse(StrUtil.EMPTY),
nodeProjectInfoModel.getAbsoluteLog());
absoluteLog);
}
@Override
public CommandOpResult stopJava(NodeProjectInfoModel nodeProjectInfoModel, int pid) throws Exception {
public CommandOpResult stopJava(NodeProjectInfoModel nodeProjectInfoModel, NodeProjectInfoModel originalModel, int pid) {
String tag = nodeProjectInfoModel.getId();
List<String> result = new ArrayList<>();
boolean success = false;
// 如果正在运行则执行杀进程命令
String kill = AbstractSystemCommander.getInstance().kill(FileUtil.file(nodeProjectInfoModel.allLib()), pid);
File file = projectInfoService.resolveLibFile(nodeProjectInfoModel);
String kill = systemCommander.kill(file, pid);
result.add(kill);
if (this.loopCheckRun(nodeProjectInfoModel, false)) {
if (this.loopCheckRun(nodeProjectInfoModel, originalModel, false)) {
success = true;
} else {
result.add("Kill not completed");
@ -117,4 +136,7 @@ public class WindowsProjectCommander extends AbstractProjectCommander {
}
return array;
}
// tasklist | findstr /s /i "java"
// wmic process where caption="javaw.exe" get processid,caption,commandline /value
}

View File

@ -22,9 +22,11 @@
*/
package org.dromara.jpom.common.commander.impl;
import cn.hutool.core.util.StrUtil;
import org.dromara.jpom.common.commander.AbstractSystemCommander;
import org.dromara.jpom.common.commander.Commander;
import org.dromara.jpom.util.CommandUtil;
import org.springframework.context.annotation.Conditional;
import org.springframework.stereotype.Service;
import java.io.File;
@ -34,6 +36,8 @@ import java.io.File;
* @author bwcx_jzy
* @since 2019/4/16
*/
@Conditional(Commander.Windows.class)
@Service
public class WindowsSystemCommander extends AbstractSystemCommander {
@Override
@ -42,23 +46,23 @@ public class WindowsSystemCommander extends AbstractSystemCommander {
}
@Override
public boolean getServiceStatus(String serviceName) {
String result = CommandUtil.execSystemCommand("sc query " + serviceName);
return StrUtil.containsIgnoreCase(result, "RUNNING");
}
@Override
public String startService(String serviceName) {
String format = StrUtil.format("net start {}", serviceName);
return CommandUtil.execSystemCommand(format);
}
@Override
public String stopService(String serviceName) {
String format = StrUtil.format("net stop {}", serviceName);
return CommandUtil.execSystemCommand(format);
}
// @Override
// public boolean getServiceStatus(String serviceName) {
// String result = CommandUtil.execSystemCommand("sc query " + serviceName);
// return StrUtil.containsIgnoreCase(result, "RUNNING");
// }
//
// @Override
// public String startService(String serviceName) {
// String format = StrUtil.format("net start {}", serviceName);
// return CommandUtil.execSystemCommand(format);
// }
//
// @Override
// public String stopService(String serviceName) {
// String format = StrUtil.format("net stop {}", serviceName);
// return CommandUtil.execSystemCommand(format);
// }
@Override
public String buildKill(int pid) {

View File

@ -26,7 +26,8 @@ import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.servlet.ServletUtil;
import cn.keepbx.jpom.model.JsonMessage;
import org.dromara.jpom.common.Const;
import org.dromara.jpom.system.AgentAuthorize;
import org.dromara.jpom.configuration.AgentConfig;
import org.dromara.jpom.configuration.AgentAuthorize;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.web.method.HandlerMethod;
@ -45,8 +46,8 @@ public class AuthorizeInterceptor implements HandlerMethodInterceptor {
private final AgentAuthorize agentAuthorize;
public AuthorizeInterceptor(AgentAuthorize agentAuthorize) {
this.agentAuthorize = agentAuthorize;
public AuthorizeInterceptor(AgentConfig agentConfig) {
this.agentAuthorize = agentConfig.getAuthorize();
}
@Override

View File

@ -20,7 +20,7 @@
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package org.dromara.jpom.system;
package org.dromara.jpom.configuration;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.CharsetUtil;
@ -32,13 +32,11 @@ import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.dromara.jpom.JpomApplication;
import org.dromara.jpom.common.Const;
import org.dromara.jpom.common.ILoadEvent;
import org.dromara.jpom.model.system.AgentAutoUser;
import org.dromara.jpom.system.JpomRuntimeException;
import org.dromara.jpom.util.JsonFileUtil;
import org.dromara.jpom.util.JvmUtil;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
import java.io.File;
@ -50,9 +48,8 @@ import java.io.File;
*/
@Slf4j
@Data
@Configuration
@ConfigurationProperties("jpom.authorize")
public class AgentAuthorize implements ILoadEvent {
public class AgentAuthorize {
/**
* 账号
*/
@ -74,17 +71,6 @@ public class AgentAuthorize implements ILoadEvent {
return null;
}
private final JpomApplication configBean;
/**
* 注入控制加载顺序必须先加载数据目录才能初始化
*/
private final AgentConfig agentConfig;
public AgentAuthorize(JpomApplication configBean,
AgentConfig agentConfig) {
this.configBean = configBean;
this.agentConfig = agentConfig;
}
/**
* 判断授权是否正确
@ -99,7 +85,7 @@ public class AgentAuthorize implements ILoadEvent {
/**
* 检查是否配置密码
*/
private void checkPwd() {
private void checkPwd(JpomApplication configBean) {
File path = FileUtil.file(configBean.getDataPath(), Const.AUTHORIZE);
if (StrUtil.isNotEmpty(agentPwd)) {
// 有指定密码 清除旧密码信息
@ -130,14 +116,12 @@ public class AgentAuthorize implements ILoadEvent {
log.info("Automatically generate authorized account:{} password:{} Authorization information storage location{}", this.agentName, this.agentPwd, FileUtil.getAbsolutePath(path));
}
@Override
public void afterPropertiesSet(ApplicationContext applicationContext) throws Exception {
// 登录名不能为空
public void init(JpomApplication configBean) {
if (StrUtil.isEmpty(this.agentName)) {
throw new JpomRuntimeException("The agent login name cannot be empty");
}
if (StrUtil.isEmpty(this.authorize)) {
this.checkPwd();
this.checkPwd(configBean);
// 生成密码授权字符串
this.authorize = SecureUtil.sha1(this.agentName + "@" + this.agentPwd);
} else {
@ -146,9 +130,4 @@ public class AgentAuthorize implements ILoadEvent {
//
JvmUtil.checkJpsNormal();
}
@Override
public int getOrder() {
return HIGHEST_PRECEDENCE;
}
}

View File

@ -20,23 +20,24 @@
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package org.dromara.jpom.system;
package org.dromara.jpom.configuration;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.system.SystemUtil;
import cn.hutool.core.util.ObjectUtil;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.jpom.JpomApplication;
import org.dromara.jpom.common.ILoadEvent;
import org.dromara.jpom.system.ExtConfigBean;
import org.dromara.jpom.util.BaseFileTailWatcher;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.util.unit.DataSize;
import java.io.File;
import java.nio.charset.Charset;
import java.util.Optional;
/**
@ -45,11 +46,12 @@ import java.util.Optional;
* @author bwcx_jzy
* @since 2022/12/16
*/
@EqualsAndHashCode(callSuper = true)
@Configuration
@ConfigurationProperties("jpom")
@Data
public class AgentConfig extends BaseExtConfig {
@EnableConfigurationProperties({ProjectConfig.class, ProjectLogConfig.class, SystemConfig.class, AgentAuthorize.class})
public class AgentConfig implements ILoadEvent, InitializingBean {
private final JpomApplication jpomApplication;
@ -58,21 +60,34 @@ public class AgentConfig extends BaseExtConfig {
}
/**
* 白名单配置
* 授权配置
*/
private WhitelistDirectory whitelist;
public WhitelistDirectory getWhitelist() {
return Optional.ofNullable(this.whitelist).orElseGet(() -> {
this.whitelist = new WhitelistDirectory();
return this.whitelist;
});
}
private AgentAuthorize authorize;
/**
* 项目配置
*/
private ProjectConfig project;
/**
* 系统配置参数
*/
private SystemConfig system;
/**
* 数据目录
*/
private String path;
/**
* 初始读取日志文件行号
*/
private int initReadLine = 10;
public AgentAuthorize getAuthorize() {
return Optional.ofNullable(this.authorize).orElseGet(() -> {
this.authorize = new AgentAuthorize();
return this.authorize;
});
}
public ProjectConfig getProject() {
return Optional.ofNullable(this.project).orElseGet(() -> {
@ -81,13 +96,6 @@ public class AgentConfig extends BaseExtConfig {
});
}
/**
* 系统配置参数
*/
private SystemConfig system;
public SystemConfig getSystem() {
return Optional.ofNullable(this.system).orElseGet(() -> {
this.system = new SystemConfig();
@ -129,95 +137,22 @@ public class AgentConfig extends BaseExtConfig {
return file;
}
@Data
public static class ProjectConfig {
private LogConfig log;
public LogConfig getLog() {
return Optional.ofNullable(this.log).orElseGet(() -> {
this.log = new LogConfig();
return this.log;
});
}
/**
* 停止项目等待的时长 单位秒最小为1秒
*/
private int statusWaitTime = 10;
/**
* 项目状态检测间隔时间 单位毫秒最小为1毫秒
*/
private int statusDetectionInterval = 500;
/**
* 项目文件备份保留个数,大于 1 才会备份
*/
private int fileBackupCount;
/**
* 限制备份指定文件后缀支持正则
* [ '.jar','.html','^.+\\.(?i)(txt)$' ]
*/
private String[] fileBackupSuffix;
@Data
public static class LogConfig {
/**
* 检测控制台日志周期防止日志文件过大目前暂只支持linux 不停服备份
*/
private String autoBackupConsoleCron = "0 0/10 * * * ?";
/**
* 当文件多大时自动备份
*
* @see ch.qos.logback.core.util.FileSize
*/
private DataSize autoBackupSize = DataSize.ofMegabytes(50);
/**
* 是否自动将控制台日志文件备份
*/
private boolean autoBackupToFile = true;
/**
* 控制台日志保存时长单位天
*/
private int saveDays = 7;
public int getSaveDays() {
return Math.max(saveDays, 0);
}
/**
* 日志文件的编码格式
*/
private Charset fileCharset;
public Charset getFileCharset() {
return Optional.ofNullable(this.fileCharset).orElseGet(() ->
SystemUtil.getOsInfo().isWindows() ?
CharsetUtil.CHARSET_GBK : CharsetUtil.CHARSET_UTF_8);
}
}
public static ProjectConfig getInstance() {
AgentConfig agentConfig = SpringUtil.getBean(AgentConfig.class);
return agentConfig.getProject();
}
@Override
public void afterPropertiesSet(ApplicationContext applicationContext) throws Exception {
// 登录名不能为空
this.getAuthorize().init(jpomApplication);
}
@Data
public static class WhitelistDirectory {
/**
* 白名单目录是否验证包含关系
*/
private boolean checkStartsWith = true;
@Override
public int getOrder() {
return HIGHEST_PRECEDENCE;
}
@EqualsAndHashCode(callSuper = true)
@Data
public static class SystemConfig extends BaseSystemConfig {
@Override
public void afterPropertiesSet() throws Exception {
int initReadLine = ObjectUtil.defaultIfNull(this.initReadLine, 10);
BaseFileTailWatcher.setInitReadLine(initReadLine);
ExtConfigBean.setPath(path);
}
}

View File

@ -0,0 +1,68 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2019 Code Technology Studio
*
* 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:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* 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.
*/
package org.dromara.jpom.configuration;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import java.util.Optional;
/**
* @author bwcx_jzy
* @since 23/12/29 029
*/
@Data
@ConfigurationProperties("jpom.project")
public class ProjectConfig {
/**
* 项目日志配置
*/
private ProjectLogConfig log;
/**
* 停止项目等待的时长 单位秒最小为1秒
*/
private int statusWaitTime = 10;
/**
* 项目状态检测间隔时间 单位毫秒最小为1毫秒
*/
private int statusDetectionInterval = 500;
/**
* 项目文件备份保留个数,大于 1 才会备份
*/
private int fileBackupCount;
/**
* 限制备份指定文件后缀支持正则
* [ '.jar','.html','^.+\\.(?i)(txt)$' ]
*/
private String[] fileBackupSuffix;
public ProjectLogConfig getLog() {
return Optional.ofNullable(this.log).orElseGet(() -> {
this.log = new ProjectLogConfig();
return this.log;
});
}
}

View File

@ -0,0 +1,75 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2019 Code Technology Studio
*
* 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:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* 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.
*/
package org.dromara.jpom.configuration;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.system.SystemUtil;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.util.unit.DataSize;
import java.nio.charset.Charset;
import java.util.Optional;
/**
* @author bwcx_jzy
* @since 23/12/29 029
*/
@Data
@ConfigurationProperties("jpom.project.log")
public class ProjectLogConfig {
/**
* 检测控制台日志周期防止日志文件过大目前暂只支持linux 不停服备份
*/
private String autoBackupConsoleCron = "0 0/10 * * * ?";
/**
* 当文件多大时自动备份
*
* @see ch.qos.logback.core.util.FileSize
*/
private DataSize autoBackupSize = DataSize.ofMegabytes(50);
/**
* 是否自动将控制台日志文件备份
*/
private boolean autoBackupToFile = true;
/**
* 控制台日志保存时长单位天
*/
private int saveDays = 7;
public int getSaveDays() {
return Math.max(saveDays, 0);
}
/**
* 日志文件的编码格式
*/
private Charset fileCharset;
public Charset getFileCharset() {
return Optional.ofNullable(this.fileCharset).orElseGet(() ->
SystemUtil.getOsInfo().isWindows() ?
CharsetUtil.CHARSET_GBK : CharsetUtil.CHARSET_UTF_8);
}
}

View File

@ -0,0 +1,38 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2019 Code Technology Studio
*
* 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:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* 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.
*/
package org.dromara.jpom.configuration;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.jpom.system.BaseSystemConfig;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* @author bwcx_jzy
* @since 23/12/29 029
*/
@EqualsAndHashCode(callSuper = true)
@Data
@ConfigurationProperties("jpom.system")
public class SystemConfig extends BaseSystemConfig {
}

View File

@ -33,8 +33,8 @@ import lombok.extern.slf4j.Slf4j;
import org.dromara.jpom.common.BaseAgentController;
import org.dromara.jpom.common.JpomManifest;
import org.dromara.jpom.common.RemoteVersion;
import org.dromara.jpom.common.commander.AbstractProjectCommander;
import org.dromara.jpom.common.commander.AbstractSystemCommander;
import org.dromara.jpom.common.commander.ProjectCommander;
import org.dromara.jpom.common.commander.SystemCommander;
import org.dromara.jpom.common.interceptor.NotAuthorize;
import org.dromara.jpom.model.data.NodeProjectInfoModel;
import org.dromara.jpom.model.data.NodeScriptModel;
@ -50,7 +50,9 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
/**
@ -65,11 +67,17 @@ public class IndexController extends BaseAgentController {
private final ProjectInfoService projectInfoService;
private final NodeScriptServer nodeScriptServer;
private final SystemCommander systemCommander;
private final ProjectCommander projectCommander;
public IndexController(ProjectInfoService projectInfoService,
NodeScriptServer nodeScriptServer) {
NodeScriptServer nodeScriptServer,
SystemCommander systemCommander,
ProjectCommander projectCommander) {
this.projectInfoService = projectInfoService;
this.nodeScriptServer = nodeScriptServer;
this.systemCommander = systemCommander;
this.projectCommander = projectCommander;
}
@RequestMapping(value = {"index", "", "index.html", "/"}, produces = MediaType.TEXT_PLAIN_VALUE)
@ -125,9 +133,33 @@ public class IndexController extends BaseAgentController {
jsonObject.put("totalMemory", SystemUtil.getTotalMemory());
//
jsonObject.put("freeMemory", SystemUtil.getFreeMemory());
Map<String, JSONObject> workspaceMap = new HashMap<>(4);
//
jsonObject.put("projectCount", CollUtil.size(nodeProjectInfoModels));
jsonObject.put("scriptCount", CollUtil.size(list));
{
for (NodeProjectInfoModel model : nodeProjectInfoModels) {
JSONObject jsonObject1 = workspaceMap.computeIfAbsent(model.getWorkspaceId(), s -> {
JSONObject jsonObject11 = new JSONObject();
jsonObject11.put("projectCount", 0);
jsonObject11.put("scriptCount", 0);
return jsonObject11;
});
jsonObject1.merge("projectCount", 1, (v1, v2) -> Integer.sum((Integer) v1, (Integer) v2));
}
jsonObject.put("projectCount", CollUtil.size(nodeProjectInfoModels));
}
{
for (NodeScriptModel model : list) {
JSONObject jsonObject1 = workspaceMap.computeIfAbsent(model.getWorkspaceId(), s -> {
JSONObject jsonObject11 = new JSONObject();
jsonObject11.put("projectCount", 0);
jsonObject11.put("scriptCount", 0);
return jsonObject11;
});
jsonObject1.merge("scriptCount", 1, (v1, v2) -> Integer.sum((Integer) v1, (Integer) v2));
}
jsonObject.put("scriptCount", CollUtil.size(list));
}
jsonObject.put("workspaceStat", workspaceMap);
return jsonObject;
}
@ -139,7 +171,7 @@ public class IndexController extends BaseAgentController {
processes = processes.stream()
.peek(jsonObject -> {
int processId = jsonObject.getIntValue("processId");
String port = AbstractProjectCommander.getInstance().getMainPort(processId);
String port = projectCommander.getMainPort(processId);
jsonObject.put("port", port);
//
})
@ -152,7 +184,7 @@ public class IndexController extends BaseAgentController {
public IJsonMessage<String> kill(int pid) {
long jpomAgentId = JpomManifest.getInstance().getPid();
Assert.state(!StrUtil.equals(StrUtil.toString(jpomAgentId), StrUtil.toString(pid)), "不支持在线关闭 Agent 进程");
String result = AbstractSystemCommander.getInstance().kill(null, pid);
String result = systemCommander.kill(null, pid);
if (StrUtil.isEmpty(result)) {
result = "成功kill";
}

View File

@ -30,7 +30,7 @@ import lombok.extern.slf4j.Slf4j;
import org.dromara.jpom.common.BaseAgentController;
import org.dromara.jpom.common.commander.CommandOpResult;
import org.dromara.jpom.common.validator.ValidatorItem;
import org.dromara.jpom.system.AgentConfig;
import org.dromara.jpom.configuration.AgentConfig;
import org.dromara.jpom.util.CompressionFileUtil;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestMapping;

View File

@ -26,18 +26,18 @@ import cn.hutool.core.convert.Convert;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.RegexPool;
import cn.hutool.core.lang.Validator;
import cn.hutool.core.util.EnumUtil;
import cn.hutool.core.util.StrUtil;
import cn.keepbx.jpom.IJsonMessage;
import cn.keepbx.jpom.model.JsonMessage;
import lombok.extern.slf4j.Slf4j;
import org.dromara.jpom.common.BaseAgentController;
import org.dromara.jpom.common.Const;
import org.dromara.jpom.common.commander.AbstractProjectCommander;
import org.dromara.jpom.common.commander.ProjectCommander;
import org.dromara.jpom.model.RunMode;
import org.dromara.jpom.model.data.DslYmlDto;
import org.dromara.jpom.model.data.NodeProjectInfoModel;
import org.dromara.jpom.service.WhitelistDirectoryService;
import org.dromara.jpom.socket.ConsoleCommandOp;
import org.dromara.jpom.util.CommandUtil;
import org.dromara.jpom.util.FileUtils;
import org.springframework.http.MediaType;
@ -61,70 +61,78 @@ import java.util.List;
public class ManageEditProjectController extends BaseAgentController {
private final WhitelistDirectoryService whitelistDirectoryService;
private final ProjectCommander projectCommander;
public ManageEditProjectController(WhitelistDirectoryService whitelistDirectoryService) {
public ManageEditProjectController(WhitelistDirectoryService whitelistDirectoryService,
ProjectCommander projectCommander) {
this.whitelistDirectoryService = whitelistDirectoryService;
this.projectCommander = projectCommander;
}
/**
* 基础检查
*
* @param projectInfo 项目实体
* @param whitelistDirectory 白名单
* @param previewData 预检查数据
* @param projectInfo 项目实体
* @param previewData 预检查数据
*/
private NodeProjectInfoModel checkParameter(NodeProjectInfoModel projectInfo,
String whitelistDirectory,
boolean previewData) {
private void checkParameter(NodeProjectInfoModel projectInfo, boolean previewData) {
String id = projectInfo.getId();
// 兼容 _
String checkId = StrUtil.replace(id, StrUtil.DASHED, StrUtil.UNDERLINE);
Validator.validateGeneral(checkId, 2, Const.ID_MAX_LEN, "项目id 长度范围2-20英文字母 、数字和下划线)");
Assert.state(!Const.SYSTEM_ID.equals(id), "项目id " + Const.SYSTEM_ID + " 关键词被系统占用");
// 运行模式
String runModeStr = getParameter("runMode");
RunMode runMode1 = EnumUtil.fromString(RunMode.class, runModeStr, RunMode.ClassPath);
projectInfo.setRunMode(runMode1);
RunMode runMode = projectInfo.getRunMode();
Assert.notNull(runMode, "请选择运行模式");
// 监测
if (runMode1 == RunMode.ClassPath || runMode1 == RunMode.JavaExtDirsCp) {
Assert.hasText(projectInfo.getMainClass(), "ClassPath、JavaExtDirsCp 模式 MainClass必填");
} else if (runMode1 == RunMode.Jar || runMode1 == RunMode.JarWar) {
if (runMode == RunMode.ClassPath || runMode == RunMode.JavaExtDirsCp) {
Assert.hasText(projectInfo.mainClass(), "ClassPath、JavaExtDirsCp 模式 MainClass必填");
if (runMode == RunMode.JavaExtDirsCp) {
Assert.hasText(projectInfo.javaExtDirsCp(), "JavaExtDirsCp 模式 javaExtDirsCp必填");
}
} else if (runMode == RunMode.Jar || runMode == RunMode.JarWar) {
projectInfo.setMainClass(StrUtil.EMPTY);
}
if (runMode1 == RunMode.JavaExtDirsCp) {
Assert.hasText(projectInfo.getJavaExtDirsCp(), "JavaExtDirsCp 模式 javaExtDirsCp必填");
} else if (runMode == RunMode.Link) {
String linkId = projectInfo.getLinkId();
Assert.hasText(linkId, "Link 模式 LinkId必填");
NodeProjectInfoModel item = projectInfoService.getItem(linkId);
Assert.notNull(item, "软链的项目部存在");
RunMode itemRunMode = item.getRunMode();
Assert.state(itemRunMode != RunMode.File && itemRunMode != RunMode.Link, "被软链的项目不能是File或Link模式");
}
// 判断是否为分发添加
String strOutGivingProject = getParameter("outGivingProject");
boolean outGivingProject = Boolean.parseBoolean(strOutGivingProject);
projectInfo.setOutGivingProject(outGivingProject);
if (!previewData) {
// 不是预检查数据才效验白名单
if (!whitelistDirectoryService.checkProjectDirectory(whitelistDirectory)) {
if (outGivingProject) {
whitelistDirectoryService.addProjectWhiteList(whitelistDirectory);
} else {
throw new IllegalArgumentException("请选择正确的项目路径,或者还没有配置白名单");
}
}
String logPath = projectInfo.getLogPath();
if (StrUtil.isNotEmpty(logPath)) {
if (!whitelistDirectoryService.checkProjectDirectory(logPath)) {
if (runMode != RunMode.Link) {
if (!previewData) {
String whitelistDirectory = projectInfo.whitelistDirectory();
// 不是预检查数据才效验授权
if (!whitelistDirectoryService.checkProjectDirectory(whitelistDirectory)) {
if (outGivingProject) {
whitelistDirectoryService.addProjectWhiteList(logPath);
whitelistDirectoryService.addProjectWhiteList(whitelistDirectory);
} else {
throw new IllegalArgumentException("请填写的项目日志存储路径,或者还没有配置白名单");
throw new IllegalArgumentException("请选择正确的项目路径,或者还没有配置授权");
}
}
String logPath = projectInfo.logPath();
if (StrUtil.isNotEmpty(logPath)) {
if (!whitelistDirectoryService.checkProjectDirectory(logPath)) {
if (outGivingProject) {
whitelistDirectoryService.addProjectWhiteList(logPath);
} else {
throw new IllegalArgumentException("请填写的项目日志存储路径,或者还没有配置授权");
}
}
}
}
}
//
String lib = projectInfo.getLib();
Assert.state(StrUtil.isNotEmpty(lib) && !StrUtil.SLASH.equals(lib) && !Validator.isChinese(lib), "项目路径不能为空,不能为顶级目录,不能包含中文");
//
String lib = projectInfo.getLib();
Assert.state(StrUtil.isNotEmpty(lib) && !StrUtil.SLASH.equals(lib) && !Validator.isChinese(lib), "项目路径不能为空,不能为顶级目录,不能包含中文");
FileUtils.checkSlip(lib, e -> new IllegalArgumentException("项目路径存在提升目录问题"));
return projectInfoService.getItem(projectInfo.getId());
FileUtils.checkSlip(lib, e -> new IllegalArgumentException("项目路径存在提升目录问题"));
}
}
@ -133,40 +141,22 @@ public class ManageEditProjectController extends BaseAgentController {
// 预检查数据
String strPreviewData = getParameter("previewData");
boolean previewData = Convert.toBool(strPreviewData, false);
String whitelistDirectory = projectInfo.getWhitelistDirectory();
//
NodeProjectInfoModel exits = this.checkParameter(projectInfo, whitelistDirectory, previewData);
String id = projectInfo.getId();
//
String allLib = projectInfo.allLib();
// 判断空格
Assert.state(!id.contains(StrUtil.SPACE) && !allLib.contains(StrUtil.SPACE), "项目Id、项目路径不能包含空格");
File checkFile = FileUtil.file(allLib);
Assert.state(!FileUtil.exist(checkFile) || FileUtil.isDirectory(checkFile), "项目路径是一个已经存在的文件");
// 重复lib
List<NodeProjectInfoModel> list = projectInfoService.list();
if (list != null) {
for (NodeProjectInfoModel nodeProjectInfoModel : list) {
File fileLib = FileUtil.file(nodeProjectInfoModel.allLib());
if (!nodeProjectInfoModel.getId().equals(id) && FileUtil.equals(fileLib, checkFile)) {
return new JsonMessage<>(401, "当前项目路径已经被【" + nodeProjectInfoModel.getName() + "】占用,请检查");
}
}
}
this.checkParameter(projectInfo, previewData);
// 自动生成log文件
String log = projectInfo.getLog();
Assert.hasText(log, "项目log解析读取失败");
checkFile = FileUtil.file(log);
File checkFile = this.projectInfoService.resolveAbsoluteLogFile(projectInfo);
Assert.state(!FileUtil.exist(checkFile) || FileUtil.isFile(checkFile), "项目log是一个已经存在的文件夹");
//
String token = projectInfo.getToken();
String token = projectInfo.token();
if (StrUtil.isNotEmpty(token)) {
Validator.validateMatchRegex(RegexPool.URL_HTTP, token, "WebHooks 地址不合法");
}
// 判断 yml
this.checkDslYml(projectInfo);
//
return save(projectInfo, exits, previewData);
return this.save(projectInfo, previewData);
}
private void checkDslYml(NodeProjectInfoModel projectInfo) {
@ -174,7 +164,7 @@ public class ManageEditProjectController extends BaseAgentController {
String dslContent = projectInfo.getDslContent();
Assert.hasText(dslContent, "请配置 dsl 内容");
DslYmlDto build = DslYmlDto.build(dslContent);
Assert.state(build.hasRunProcess("status"), "没有配置 run.status");
Assert.state(build.hasRunProcess(ConsoleCommandOp.status.name()), "没有配置 run.status");
}
}
@ -185,15 +175,16 @@ public class ManageEditProjectController extends BaseAgentController {
* @param previewData 是否是预检查
* @return 错误信息
*/
private IJsonMessage<String> save(NodeProjectInfoModel projectInfo, NodeProjectInfoModel exits, boolean previewData) {
projectInfo.setWorkspaceId(getWorkspaceId());
private IJsonMessage<String> save(NodeProjectInfoModel projectInfo, boolean previewData) {
this.checkPath(projectInfo);
//
NodeProjectInfoModel exits = projectInfoService.getItem(projectInfo.getId());
projectInfo.setWorkspaceId(this.getWorkspaceId());
RunMode runMode = projectInfo.getRunMode();
if (exits == null) {
// 检查运行中的tag 是否被占用
if (runMode != RunMode.File && runMode != RunMode.Dsl) {
Assert.state(!AbstractProjectCommander.getInstance().isRun(projectInfo), "当前项目id已经被正在运行的程序占用");
Assert.state(!projectCommander.isRun(projectInfo), "当前项目id已经被正在运行的程序占用");
}
if (previewData) {
// 预检查数据
@ -207,29 +198,34 @@ public class ManageEditProjectController extends BaseAgentController {
// 预检查数据
return JsonMessage.success("检查通过");
} else {
exits.setNodeId(projectInfo.getNodeId());
exits.setName(projectInfo.getName());
exits.setGroup(projectInfo.getGroup());
exits.setAutoStart(projectInfo.getAutoStart());
exits.setMainClass(projectInfo.getMainClass());
exits.setMainClass(projectInfo.mainClass());
exits.setJvm(projectInfo.getJvm());
exits.setArgs(projectInfo.getArgs());
exits.setWorkspaceId(this.getWorkspaceId());
exits.setOutGivingProject(projectInfo.isOutGivingProject());
exits.setRunMode(runMode);
exits.setToken(projectInfo.getToken());
exits.setToken(projectInfo.token());
exits.setDslContent(projectInfo.getDslContent());
exits.setDslEnv(projectInfo.getDslEnv());
exits.setJavaExtDirsCp(projectInfo.getJavaExtDirsCp());
// 移动到新路径
this.moveTo(exits, projectInfo);
// 最后才设置新的路径
exits.setLib(projectInfo.getLib());
exits.setWhitelistDirectory(projectInfo.getWhitelistDirectory());
//
exits.setLog(projectInfo.getLog());
exits.setLogPath(projectInfo.getLogPath());
projectInfoService.updateItem(exits);
exits.setJavaExtDirsCp(projectInfo.javaExtDirsCp());
if (runMode == RunMode.Link) {
// 如果是链接模式
Assert.state(runMode == exits.getRunMode(), "已经存在的项目不能修改为软链项目");
} else {
// 移动到新路径
this.moveTo(exits, projectInfo);
// 最后才设置新的路径
exits.setLib(projectInfo.getLib());
exits.setWhitelistDirectory(projectInfo.whitelistDirectory());
//
//exits.setLog(projectInfo.log());
exits.setLogPath(projectInfo.logPath());
}
projectInfoService.updateById(exits, exits.getId());
return JsonMessage.success("修改成功");
}
}
@ -237,8 +233,8 @@ public class ManageEditProjectController extends BaseAgentController {
private void moveTo(NodeProjectInfoModel old, NodeProjectInfoModel news) {
{
// 移动目录
File oldLib = FileUtil.file(old.allLib());
File newLib = FileUtil.file(news.allLib());
File oldLib = projectInfoService.resolveLibFile(old);
File newLib = projectInfoService.resolveLibFile(news);
if (!FileUtil.equals(oldLib, newLib)) {
// 正在运行的项目不能修改路径
this.projectMustNotRun(old, "正在运行的项目不能修改路径");
@ -249,8 +245,8 @@ public class ManageEditProjectController extends BaseAgentController {
}
{
// log
File oldLog = FileUtil.file(old.getLog());
File newLog = FileUtil.file(news.getLog());
File oldLog = projectInfoService.resolveAbsoluteLogFile(old);
File newLog = projectInfoService.resolveAbsoluteLogFile(news);
if (!FileUtil.equals(oldLog, newLog)) {
// 正在运行的项目不能修改路径
this.projectMustNotRun(old, "正在运行的项目不能修改路径");
@ -259,9 +255,9 @@ public class ManageEditProjectController extends BaseAgentController {
FileUtil.move(oldLog, newLog, true);
}
// logBack
File oldLogBack = old.getLogBack();
File oldLogBack = projectInfoService.resolveLogBack(old);
if (oldLogBack.exists()) {
File logBack = news.getLogBack();
File logBack = projectInfoService.resolveLogBack(news);
FileUtils.tempMoveContent(oldLogBack, logBack);
}
}
@ -269,7 +265,7 @@ public class ManageEditProjectController extends BaseAgentController {
}
private void projectMustNotRun(NodeProjectInfoModel projectInfoModel, String msg) {
boolean run = AbstractProjectCommander.getInstance().isRun(projectInfoModel);
boolean run = projectCommander.isRun(projectInfoModel);
Assert.state(!run, msg);
}
@ -279,15 +275,41 @@ public class ManageEditProjectController extends BaseAgentController {
* @param nodeProjectInfoModel 比较的项目
*/
private void checkPath(NodeProjectInfoModel nodeProjectInfoModel) {
List<NodeProjectInfoModel> nodeProjectInfoModelList = projectInfoService.list();
if (nodeProjectInfoModelList == null) {
String id = nodeProjectInfoModel.getId();
Assert.state(!id.contains(StrUtil.SPACE), "项目Id不能包含空格");
RunMode runMode = nodeProjectInfoModel.getRunMode();
if (runMode == RunMode.Link) {
return;
}
List<NodeProjectInfoModel> list = projectInfoService.list();
if (list == null) {
return;
}
//
String allLib = nodeProjectInfoModel.allLib();
// 判断空格
Assert.state(!allLib.contains(StrUtil.SPACE), "项目路径不能包含空格");
File checkFile = FileUtil.file(allLib);
Assert.state(!FileUtil.exist(checkFile) || FileUtil.isDirectory(checkFile), "项目路径是一个已经存在的文件");
// 重复lib
for (NodeProjectInfoModel item : list) {
if (item.getRunMode() == RunMode.Link) {
continue;
}
File fileLib = projectInfoService.resolveLibFile(item);
if (!nodeProjectInfoModel.getId().equals(id) && FileUtil.equals(fileLib, checkFile)) {
throw new IllegalArgumentException("当前项目路径已经被【" + item.getName() + "】占用,请检查");
}
}
NodeProjectInfoModel nodeProjectInfoModel1 = null;
for (NodeProjectInfoModel model : nodeProjectInfoModelList) {
for (NodeProjectInfoModel model : list) {
if (model.getRunMode() == RunMode.Link) {
continue;
}
if (!model.getId().equals(nodeProjectInfoModel.getId())) {
File file1 = new File(model.allLib());
File file2 = new File(nodeProjectInfoModel.allLib());
File file1 = projectInfoService.resolveLibFile(model);
File file2 = projectInfoService.resolveLibFile(nodeProjectInfoModel);
if (FileUtil.pathEquals(file1, file2)) {
nodeProjectInfoModel1 = model;
break;
@ -310,15 +332,23 @@ public class ManageEditProjectController extends BaseAgentController {
* @return json
*/
@RequestMapping(value = "deleteProject", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
public IJsonMessage<String> deleteProject(String thorough) {
public IJsonMessage<String> deleteProject(String id, String thorough) {
NodeProjectInfoModel nodeProjectInfoModel = tryGetProjectInfoModel();
if (nodeProjectInfoModel == null) {
// 返回正常 200 状态码考虑节点分发重复操作
return JsonMessage.success("项目不存在");
}
// 运行判断
boolean run = AbstractProjectCommander.getInstance().isRun(nodeProjectInfoModel);
boolean run = projectCommander.isRun(nodeProjectInfoModel);
Assert.state(!run, "不能删除正在运行的项目");
// 判断是否被软链
List<NodeProjectInfoModel> list = projectInfoService.list();
for (NodeProjectInfoModel projectInfoModel : list) {
if (nodeProjectInfoModel.getRunMode() != RunMode.Link) {
continue;
}
Assert.state(!StrUtil.equals(projectInfoModel.getLinkId(), id), "项目被" + projectInfoModel.getName() + "软链中");
}
this.thorough(thorough, nodeProjectInfoModel);
//
projectInfoService.deleteItem(nodeProjectInfoModel.getId());
@ -337,24 +367,28 @@ public class ManageEditProjectController extends BaseAgentController {
if (StrUtil.isEmpty(thorough)) {
return;
}
File logBack = nodeProjectInfoModel.getLogBack();
File logBack = this.projectInfoService.resolveLogBack(nodeProjectInfoModel);
boolean fastDel = CommandUtil.systemFastDel(logBack);
Assert.state(!fastDel, "删除日志文件失败:" + logBack.getAbsolutePath());
File log = FileUtil.file(nodeProjectInfoModel.getAbsoluteLog());
File log = this.projectInfoService.resolveAbsoluteLogFile(nodeProjectInfoModel);
fastDel = CommandUtil.systemFastDel(log);
Assert.state(!fastDel, "删除日志文件失败:" + log.getAbsolutePath());
//
File allLib = FileUtil.file(nodeProjectInfoModel.allLib());
fastDel = CommandUtil.systemFastDel(allLib);
Assert.state(!fastDel, "删除项目文件失败:" + allLib.getAbsolutePath());
if (nodeProjectInfoModel.getRunMode() != RunMode.Link) {
// 非软链项目才删除文件
File allLib = projectInfoService.resolveLibFile(nodeProjectInfoModel);
fastDel = CommandUtil.systemFastDel(allLib);
Assert.state(!fastDel, "删除项目文件失败:" + allLib.getAbsolutePath());
}
}
@RequestMapping(value = "releaseOutGiving", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
public IJsonMessage<Object> releaseOutGiving() {
NodeProjectInfoModel nodeProjectInfoModel = tryGetProjectInfoModel();
if (nodeProjectInfoModel != null) {
nodeProjectInfoModel.setOutGivingProject(false);
projectInfoService.updateItem(nodeProjectInfoModel);
NodeProjectInfoModel update = new NodeProjectInfoModel();
update.setOutGivingProject(false);
projectInfoService.updateById(update, nodeProjectInfoModel.getId());
}
return JsonMessage.success("释放成功");
}
@ -365,9 +399,10 @@ public class ManageEditProjectController extends BaseAgentController {
Assert.hasText(newWorkspaceId, "请选择要修改的节");
NodeProjectInfoModel nodeProjectInfoModel = tryGetProjectInfoModel();
if (nodeProjectInfoModel != null) {
nodeProjectInfoModel.setNodeId(newNodeId);
nodeProjectInfoModel.setWorkspaceId(newWorkspaceId);
projectInfoService.updateItem(nodeProjectInfoModel);
NodeProjectInfoModel update = new NodeProjectInfoModel();
update.setNodeId(newNodeId);
update.setWorkspaceId(newWorkspaceId);
projectInfoService.updateById(update, nodeProjectInfoModel.getId());
}
return JsonMessage.success("修改成功");
}

View File

@ -34,7 +34,7 @@ import lombok.extern.slf4j.Slf4j;
import org.dromara.jpom.common.BaseAgentController;
import org.dromara.jpom.common.validator.ValidatorItem;
import org.dromara.jpom.model.data.NodeProjectInfoModel;
import org.dromara.jpom.script.ProjectFileBackupUtil;
import org.dromara.jpom.service.ProjectFileBackupService;
import org.dromara.jpom.util.CommandUtil;
import org.dromara.jpom.util.FileUtils;
import org.springframework.http.MediaType;
@ -63,6 +63,12 @@ import java.util.stream.Collectors;
@Slf4j
public class ProjectFileBackupController extends BaseAgentController {
private final ProjectFileBackupService projectFileBackupService;
public ProjectFileBackupController(ProjectFileBackupService projectFileBackupService) {
this.projectFileBackupService = projectFileBackupService;
}
/**
* 查询备份列表
*
@ -74,9 +80,9 @@ public class ProjectFileBackupController extends BaseAgentController {
//
NodeProjectInfoModel projectInfoModel = super.getProjectInfoModel(id);
// 合并
ProjectFileBackupUtil.margeBackupPath(projectInfoModel);
projectFileBackupService.margeBackupPath(projectInfoModel);
//
File path = ProjectFileBackupUtil.pathProject(projectInfoModel);
File path = projectFileBackupService.pathProject(projectInfoModel);
//
List<File> collect = Arrays.stream(Optional.ofNullable(path.listFiles()).orElse(new File[0]))
.filter(FileUtil::isDirectory)
@ -104,7 +110,7 @@ public class ProjectFileBackupController extends BaseAgentController {
public IJsonMessage<List<JSONObject>> backupItemFiles(String id, String path, @ValidatorItem String backupId) {
// 查询项目路径
NodeProjectInfoModel projectInfoModel = super.getProjectInfoModel();
File lib = ProjectFileBackupUtil.pathProjectBackup(projectInfoModel, backupId);
File lib = projectFileBackupService.pathProjectBackup(projectInfoModel, backupId);
File fileDir = FileUtil.file(lib, StrUtil.emptyToDefault(path, FileUtil.FILE_SEPARATOR));
//
File[] filesAll = FileUtil.exist(fileDir) ? fileDir.listFiles() : new File[]{};
@ -127,7 +133,7 @@ public class ProjectFileBackupController extends BaseAgentController {
public void download(String id, @ValidatorItem String backupId, @ValidatorItem String filename, String levelName, HttpServletResponse response) {
try {
NodeProjectInfoModel projectInfoModel = super.getProjectInfoModel();
File lib = ProjectFileBackupUtil.pathProjectBackup(projectInfoModel, backupId);
File lib = projectFileBackupService.pathProjectBackup(projectInfoModel, backupId);
File file = FileUtil.file(lib, StrUtil.emptyToDefault(levelName, FileUtil.FILE_SEPARATOR), filename);
if (file.isDirectory()) {
ServletUtil.write(response, "暂不支持下载文件夹", MediaType.TEXT_HTML_VALUE);
@ -152,7 +158,7 @@ public class ProjectFileBackupController extends BaseAgentController {
@RequestMapping(value = "backup-delete", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
public IJsonMessage<Object> deleteFile(String id, @ValidatorItem String backupId, @ValidatorItem String filename, String levelName) {
NodeProjectInfoModel projectInfoModel = super.getProjectInfoModel();
File lib = ProjectFileBackupUtil.pathProjectBackup(projectInfoModel, backupId);
File lib = projectFileBackupService.pathProjectBackup(projectInfoModel, backupId);
File file = FileUtil.file(lib, StrUtil.emptyToDefault(levelName, FileUtil.FILE_SEPARATOR), filename);
CommandUtil.systemFastDel(file);
return JsonMessage.success("删除成功");
@ -171,8 +177,8 @@ public class ProjectFileBackupController extends BaseAgentController {
@RequestMapping(value = "backup-recover", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
public IJsonMessage<Object> recoverFile(String id, @ValidatorItem String backupId, String type, String filename, String levelName) {
NodeProjectInfoModel projectInfoModel = super.getProjectInfoModel();
File backupPath = ProjectFileBackupUtil.pathProjectBackup(projectInfoModel, backupId);
String projectPath = projectInfoModel.allLib();
File backupPath = projectFileBackupService.pathProjectBackup(projectInfoModel, backupId);
File projectPath = projectInfoService.resolveLibFile(projectInfoModel);
//
File backupFile;
File projectFile;

View File

@ -37,19 +37,18 @@ import cn.keepbx.jpom.model.JsonMessage;
import com.alibaba.fastjson2.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.dromara.jpom.common.BaseAgentController;
import org.dromara.jpom.common.commander.AbstractProjectCommander;
import org.dromara.jpom.common.commander.CommandOpResult;
import org.dromara.jpom.common.commander.ProjectCommander;
import org.dromara.jpom.common.validator.ValidatorItem;
import org.dromara.jpom.controller.manage.vo.DiffFileVo;
import org.dromara.jpom.model.AfterOpt;
import org.dromara.jpom.model.BaseEnum;
import org.dromara.jpom.model.data.AgentWhitelist;
import org.dromara.jpom.model.data.NodeProjectInfoModel;
import org.dromara.jpom.script.ProjectFileBackupUtil;
import org.dromara.jpom.service.ProjectFileBackupService;
import org.dromara.jpom.service.WhitelistDirectoryService;
import org.dromara.jpom.service.manage.ConsoleService;
import org.dromara.jpom.socket.ConsoleCommandOp;
import org.dromara.jpom.system.AgentConfig;
import org.dromara.jpom.configuration.AgentConfig;
import org.dromara.jpom.util.CommandUtil;
import org.dromara.jpom.util.CompressionFileUtil;
import org.dromara.jpom.util.FileUtils;
@ -78,24 +77,26 @@ import java.util.stream.Collectors;
@Slf4j
public class ProjectFileControl extends BaseAgentController {
private final ConsoleService consoleService;
private final WhitelistDirectoryService whitelistDirectoryService;
private final AgentConfig agentConfig;
private final ProjectFileBackupService projectFileBackupService;
private final ProjectCommander projectCommander;
public ProjectFileControl(ConsoleService consoleService,
WhitelistDirectoryService whitelistDirectoryService,
AgentConfig agentConfig) {
this.consoleService = consoleService;
public ProjectFileControl(WhitelistDirectoryService whitelistDirectoryService,
AgentConfig agentConfig,
ProjectFileBackupService projectFileBackupService,
ProjectCommander projectCommander) {
this.whitelistDirectoryService = whitelistDirectoryService;
this.agentConfig = agentConfig;
this.projectFileBackupService = projectFileBackupService;
this.projectCommander = projectCommander;
}
@RequestMapping(value = "getFileList", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
public IJsonMessage<List<JSONObject>> getFileList(String id, String path) {
// 查询项目路径
NodeProjectInfoModel pim = projectInfoService.getItem(id);
Assert.notNull(pim, "查询失败:项目不存在");
String lib = pim.allLib();
NodeProjectInfoModel pim = getProjectInfoModel();
String lib = projectInfoService.resolveLibPath(pim);
File fileDir = FileUtil.file(lib, StrUtil.emptyToDefault(path, FileUtil.FILE_SEPARATOR));
boolean exist = FileUtil.exist(fileDir);
if (!exist) {
@ -129,7 +130,8 @@ public class ProjectFileControl extends BaseAgentController {
List<DiffFileVo.DiffItem> data = diffFileVo.getData();
Assert.notEmpty(data, "没有要对比的数据");
// 扫描项目目录下面的所有文件
String path = FileUtil.file(projectInfoModel.allLib(), Opt.ofBlankAble(diffFileVo.getDir()).orElse(StrUtil.SLASH)).getAbsolutePath();
File lib = projectInfoService.resolveLibFile(projectInfoModel);
String path = FileUtil.file(lib, Opt.ofBlankAble(diffFileVo.getDir()).orElse(StrUtil.SLASH)).getAbsolutePath();
List<File> files = FileUtil.loopFiles(path);
// 将所有的文件信息组装并签名
List<JSONObject> collect = files.stream().map(file -> {
@ -190,7 +192,7 @@ public class ProjectFileControl extends BaseAgentController {
// 判断是否需要先关闭项目
boolean closeFirst = BooleanUtil.toBoolean(closeFirstStr);
if (closeFirst) {
CommandOpResult result = consoleService.execCommand(ConsoleCommandOp.stop, projectInfoModel);
CommandOpResult result = projectCommander.execCommand(ConsoleCommandOp.stop, projectInfoModel);
Assert.state(result.isSuccess(), "关闭项目失败:" + result.msgStr());
}
String clearType = getParameter("clearType");
@ -239,9 +241,10 @@ public class ProjectFileControl extends BaseAgentController {
*/
private IJsonMessage<CommandOpResult> upload(File file, String type, String levelName, Integer stripComponents, String after) throws Exception {
NodeProjectInfoModel pim = getProjectInfoModel();
File lib = StrUtil.isEmpty(levelName) ? new File(pim.allLib()) : FileUtil.file(pim.allLib(), levelName);
File libFile = projectInfoService.resolveLibFile(pim);
File lib = StrUtil.isEmpty(levelName) ? libFile : FileUtil.file(libFile, levelName);
// 备份文件
String backupId = ProjectFileBackupUtil.backup(pim);
String backupId = projectFileBackupService.backup(pim);
try {
//
this.saveProjectFileBefore(lib, pim);
@ -260,15 +263,14 @@ public class ProjectFileControl extends BaseAgentController {
FileUtil.mkdir(lib);
FileUtil.move(file, lib, true);
}
AbstractProjectCommander.getInstance().asyncWebHooks(pim, null, "fileChange",
"changeEvent", "upload", "levelName", levelName, "fileType", type, "fileName", file.getName());
projectCommander.asyncWebHooks(pim, "fileChange", "changeEvent", "upload", "levelName", levelName, "fileType", type, "fileName", file.getName());
//
JsonMessage<CommandOpResult> resultJsonMessage = this.saveProjectFileAfter(after, pim);
if (resultJsonMessage != null) {
return resultJsonMessage;
}
} finally {
ProjectFileBackupUtil.checkDiff(pim, backupId);
projectFileBackupService.checkDiff(pim, backupId);
}
return JsonMessage.success("上传成功");
}
@ -280,46 +282,32 @@ public class ProjectFileControl extends BaseAgentController {
//
AfterOpt afterOpt = BaseEnum.getEnum(AfterOpt.class, Convert.toInt(after, AfterOpt.No.getCode()));
if ("restart".equalsIgnoreCase(after) || afterOpt == AfterOpt.Restart) {
CommandOpResult result = consoleService.execCommand(ConsoleCommandOp.restart, pim);
CommandOpResult result = projectCommander.execCommand(ConsoleCommandOp.restart, pim);
return new JsonMessage<>(result.isSuccess() ? 200 : 405, "上传成功并重启", result);
} else if (afterOpt == AfterOpt.Order_Restart || afterOpt == AfterOpt.Order_Must_Restart) {
CommandOpResult result = consoleService.execCommand(ConsoleCommandOp.restart, pim);
CommandOpResult result = projectCommander.execCommand(ConsoleCommandOp.restart, pim);
return new JsonMessage<>(result.isSuccess() ? 200 : 405, "上传成功并重启", result);
}
return null;
}
private boolean restart(NodeProjectInfoModel nodeProjectInfoModel, AfterOpt afterOpt) {
try {
CommandOpResult result = consoleService.execCommand(ConsoleCommandOp.restart, nodeProjectInfoModel);
if (result.isSuccess()) {
return true;
}
} catch (Exception e) {
log.error("重复失败", e);
}
// 完整重启不再继续剩余的节点项目
return afterOpt != AfterOpt.Order_Must_Restart;
}
@RequestMapping(value = "deleteFile", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
public IJsonMessage<String> deleteFile(String filename, String type, String levelName) {
NodeProjectInfoModel pim = getProjectInfoModel();
File file = FileUtil.file(pim.allLib(), StrUtil.emptyToDefault(levelName, StrUtil.SLASH));
File libFile = projectInfoService.resolveLibFile(pim);
File file = FileUtil.file(libFile, StrUtil.emptyToDefault(levelName, StrUtil.SLASH));
// 备份文件
String backupId = ProjectFileBackupUtil.backup(pim);
String backupId = projectFileBackupService.backup(pim);
try {
if ("clear".equalsIgnoreCase(type)) {
// 清空文件
if (FileUtil.clean(file)) {
AbstractProjectCommander.getInstance().asyncWebHooks(pim, null, "fileChange",
"changeEvent", "delete", "levelName", levelName, "deleteType", type, "fileName", filename);
projectCommander.asyncWebHooks(pim, "fileChange", "changeEvent", "delete", "levelName", levelName, "deleteType", type, "fileName", filename);
return JsonMessage.success("清除成功");
}
boolean run = AbstractProjectCommander.getInstance().isRun(pim);
boolean run = projectCommander.isRun(pim);
Assert.state(!run, "文件被占用,请先停止项目");
return new JsonMessage<>(500, "删除失败:" + file.getAbsolutePath());
} else {
@ -327,14 +315,13 @@ public class ProjectFileControl extends BaseAgentController {
Assert.hasText(filename, "请选择要删除的文件");
file = FileUtil.file(file, filename);
if (FileUtil.del(file)) {
AbstractProjectCommander.getInstance().asyncWebHooks(pim, null, "fileChange",
"changeEvent", "delete", "levelName", levelName, "deleteType", type, "fileName", filename);
projectCommander.asyncWebHooks(pim, "fileChange", "changeEvent", "delete", "levelName", levelName, "deleteType", type, "fileName", filename);
return JsonMessage.success("删除成功");
}
return new JsonMessage<>(500, "删除失败");
}
} finally {
ProjectFileBackupUtil.checkDiff(pim, backupId);
projectFileBackupService.checkDiff(pim, backupId);
}
}
@ -345,13 +332,14 @@ public class ProjectFileControl extends BaseAgentController {
String dir = diffFileVo.getDir();
NodeProjectInfoModel projectInfoModel = super.getProjectInfoModel(id);
// 备份文件
String backupId = ProjectFileBackupUtil.backup(projectInfoModel);
String backupId = projectFileBackupService.backup(projectInfoModel);
try {
//
List<DiffFileVo.DiffItem> data = diffFileVo.getData();
Assert.notEmpty(data, "没有要对比的数据");
File libFile = projectInfoService.resolveLibFile(projectInfoModel);
//
File path = FileUtil.file(projectInfoModel.allLib(), Opt.ofBlankAble(dir).orElse(StrUtil.SLASH));
File path = FileUtil.file(libFile, Opt.ofBlankAble(dir).orElse(StrUtil.SLASH));
for (DiffFileVo.DiffItem datum : data) {
File file = FileUtil.file(path, datum.getName());
if (FileUtil.del(file)) {
@ -359,9 +347,10 @@ public class ProjectFileControl extends BaseAgentController {
}
return new JsonMessage<>(500, "删除失败:" + file.getAbsolutePath());
}
projectCommander.asyncWebHooks(projectInfoModel, "fileChange", "changeEvent", "batch-delete", "levelName", dir);
return JsonMessage.success("删除成功");
} finally {
ProjectFileBackupUtil.checkDiff(projectInfoModel, backupId);
projectFileBackupService.checkDiff(projectInfoModel, backupId);
}
}
@ -380,7 +369,8 @@ public class ProjectFileControl extends BaseAgentController {
// 判断文件后缀
AgentWhitelist whitelist = whitelistDirectoryService.getWhitelist();
Charset charset = AgentWhitelist.checkFileSuffix(whitelist.getAllowEditSuffix(), filename);
File file = FileUtil.file(pim.allLib(), filePath, filename);
File libFile = projectInfoService.resolveLibFile(pim);
File file = FileUtil.file(libFile, filePath, filename);
String ymlString = FileUtil.readString(file, charset);
return JsonMessage.success("", ymlString);
}
@ -401,14 +391,14 @@ public class ProjectFileControl extends BaseAgentController {
AgentWhitelist whitelist = whitelistDirectoryService.getWhitelist();
Charset charset = AgentWhitelist.checkFileSuffix(whitelist.getAllowEditSuffix(), filename);
// 备份文件
String backupId = ProjectFileBackupUtil.backup(pim);
String backupId = projectFileBackupService.backup(pim);
File libFile = projectInfoService.resolveLibFile(pim);
try {
FileUtil.writeString(fileText, FileUtil.file(pim.allLib(), filePath, filename), charset);
AbstractProjectCommander.getInstance().asyncWebHooks(pim, null, "fileChange",
"changeEvent", "edit", "levelName", filePath, "fileName", filename);
FileUtil.writeString(fileText, FileUtil.file(libFile, filePath, filename), charset);
projectCommander.asyncWebHooks(pim, "fileChange", "changeEvent", "edit", "levelName", filePath, "fileName", filename);
return JsonMessage.success("文件写入成功");
} finally {
ProjectFileBackupUtil.checkDiff(pim, backupId);
projectFileBackupService.checkDiff(pim, backupId);
}
}
@ -427,9 +417,10 @@ public class ProjectFileControl extends BaseAgentController {
// if (StrUtil.isEmpty(safeFileName)) {
// return JsonMessage.getString(405, "非法操作");
// }
NodeProjectInfoModel pim = getProjectInfoModel();
File libFile = projectInfoService.resolveLibFile(pim);
try {
NodeProjectInfoModel pim = projectInfoService.getItem(id);
File file = FileUtil.file(pim.allLib(), StrUtil.emptyToDefault(levelName, FileUtil.FILE_SEPARATOR), filename);
File file = FileUtil.file(libFile, StrUtil.emptyToDefault(levelName, FileUtil.FILE_SEPARATOR), filename);
if (file.isDirectory()) {
ServletUtil.write(response, JsonMessage.getString(400, "暂不支持下载文件夹"), MediaType.APPLICATION_JSON_VALUE);
return;
@ -454,9 +445,8 @@ public class ProjectFileControl extends BaseAgentController {
@PostMapping(value = "remote_download", produces = MediaType.APPLICATION_JSON_VALUE)
public IJsonMessage<String> remoteDownload(String id, String url, String levelName, String unzip, Integer stripComponents) {
Assert.hasText(url, "请输入正确的远程地址");
NodeProjectInfoModel pim = projectInfoService.getItem(id);
NodeProjectInfoModel pim = getProjectInfoModel();
File libFile = projectInfoService.resolveLibFile(pim);
String tempPathName = agentConfig.getTempPathName();
//
String backupId = null;
@ -464,8 +454,8 @@ public class ProjectFileControl extends BaseAgentController {
File downloadFile = HttpUtil.downloadFileFromUrl(url, tempPathName);
String fileSize = FileUtil.readableFileSize(downloadFile);
// 备份文件
backupId = ProjectFileBackupUtil.backup(pim);
File file = FileUtil.file(pim.allLib(), StrUtil.emptyToDefault(levelName, FileUtil.FILE_SEPARATOR));
backupId = projectFileBackupService.backup(pim);
File file = FileUtil.file(libFile, StrUtil.emptyToDefault(levelName, FileUtil.FILE_SEPARATOR));
FileUtil.mkdir(file);
if (BooleanUtil.toBoolean(unzip)) {
// 需要解压文件
@ -481,14 +471,13 @@ public class ProjectFileControl extends BaseAgentController {
// 移动文件到对应目录
FileUtil.move(downloadFile, file, true);
}
AbstractProjectCommander.getInstance().asyncWebHooks(pim, null, "fileChange",
"changeEvent", "remoteDownload", "levelName", levelName, "fileName", file.getName(), "url", url);
projectCommander.asyncWebHooks(pim, "fileChange", "changeEvent", "remoteDownload", "levelName", levelName, "fileName", file.getName(), "url", url);
return JsonMessage.success("下载成功文件大小:" + fileSize);
} catch (Exception e) {
log.error("下载远程文件异常", e);
return new JsonMessage<>(500, "下载远程文件失败:" + e.getMessage());
} finally {
ProjectFileBackupUtil.checkDiff(pim, backupId);
projectFileBackupService.checkDiff(pim, backupId);
}
}
@ -503,9 +492,9 @@ public class ProjectFileControl extends BaseAgentController {
*/
@PostMapping(value = "new_file_folder.json", produces = MediaType.APPLICATION_JSON_VALUE)
public IJsonMessage<Object> newFileFolder(String id, String levelName, @ValidatorItem String filename, String unFolder) {
NodeProjectInfoModel projectInfoModel = projectInfoService.getItem(id);
Assert.notNull(projectInfoModel, "没有对应到项目");
File file = FileUtil.file(projectInfoModel.allLib(), StrUtil.emptyToDefault(levelName, FileUtil.FILE_SEPARATOR), filename);
NodeProjectInfoModel projectInfoModel = getProjectInfoModel();
File libFile = projectInfoService.resolveLibFile(projectInfoModel);
File file = FileUtil.file(libFile, StrUtil.emptyToDefault(levelName, FileUtil.FILE_SEPARATOR), filename);
//
Assert.state(!FileUtil.exist(file), "文件夹或者文件已存在");
boolean folder = !Convert.toBool(unFolder, false);
@ -514,8 +503,7 @@ public class ProjectFileControl extends BaseAgentController {
} else {
FileUtil.touch(file);
}
AbstractProjectCommander.getInstance().asyncWebHooks(projectInfoModel, null, "fileChange",
"changeEvent", "newFileOrFolder", "levelName", levelName, "fileName", filename, "folder", folder);
projectCommander.asyncWebHooks(projectInfoModel, "fileChange", "changeEvent", "newFileOrFolder", "levelName", levelName, "fileName", filename, "folder", folder);
return JsonMessage.success("操作成功");
}
@ -531,15 +519,15 @@ public class ProjectFileControl extends BaseAgentController {
@PostMapping(value = "rename.json", produces = MediaType.APPLICATION_JSON_VALUE)
public IJsonMessage<Object> rename(String id, String levelName, @ValidatorItem String filename, String newname) {
NodeProjectInfoModel projectInfoModel = getProjectInfoModel();
File file = FileUtil.file(projectInfoModel.allLib(), StrUtil.emptyToDefault(levelName, FileUtil.FILE_SEPARATOR), filename);
File newFile = FileUtil.file(projectInfoModel.allLib(), StrUtil.emptyToDefault(levelName, FileUtil.FILE_SEPARATOR), newname);
File libFile = projectInfoService.resolveLibFile(projectInfoModel);
File file = FileUtil.file(libFile, StrUtil.emptyToDefault(levelName, FileUtil.FILE_SEPARATOR), filename);
File newFile = FileUtil.file(libFile, StrUtil.emptyToDefault(levelName, FileUtil.FILE_SEPARATOR), newname);
Assert.state(FileUtil.exist(file), "文件不存在");
Assert.state(!FileUtil.exist(newFile), "文件名已经存在拉");
FileUtil.rename(file, newname, false);
AbstractProjectCommander.getInstance().asyncWebHooks(projectInfoModel, null, "fileChange",
"changeEvent", "rename", "levelName", levelName, "fileName", filename, "newname", newname);
projectCommander.asyncWebHooks(projectInfoModel, "fileChange", "changeEvent", "rename", "levelName", levelName, "fileName", filename, "newname", newname);
return JsonMessage.success("操作成功");
}

View File

@ -22,19 +22,26 @@
*/
package org.dromara.jpom.controller.manage;
import cn.hutool.core.lang.Tuple;
import cn.keepbx.jpom.IJsonMessage;
import cn.keepbx.jpom.model.JsonMessage;
import com.alibaba.fastjson2.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.dromara.jpom.common.BaseAgentController;
import org.dromara.jpom.common.commander.AbstractProjectCommander;
import org.dromara.jpom.common.commander.ProjectCommander;
import org.dromara.jpom.model.RunMode;
import org.dromara.jpom.model.data.DslYmlDto;
import org.dromara.jpom.model.data.NodeProjectInfoModel;
import org.dromara.jpom.service.script.DslScriptServer;
import org.dromara.jpom.socket.ConsoleCommandOp;
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 java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
/**
* 管理的信息获取接口
@ -47,6 +54,15 @@ import java.util.List;
@Slf4j
public class ProjectListController extends BaseAgentController {
private final ProjectCommander projectCommander;
private final DslScriptServer dslScriptServer;
public ProjectListController(ProjectCommander projectCommander,
DslScriptServer dslScriptServer) {
this.projectCommander = projectCommander;
this.dslScriptServer = dslScriptServer;
}
/**
* 获取项目的信息
*
@ -61,9 +77,25 @@ public class ProjectListController extends BaseAgentController {
RunMode runMode = nodeProjectInfoModel.getRunMode();
if (runMode != RunMode.Dsl && runMode != RunMode.File) {
// 返回实际执行的命令
String command = AbstractProjectCommander.getInstance().buildJavaCommand(nodeProjectInfoModel);
String command = projectCommander.buildRunCommand(nodeProjectInfoModel);
nodeProjectInfoModel.setRunCommand(command);
}
if (runMode == RunMode.Dsl) {
DslYmlDto dslYmlDto = nodeProjectInfoModel.mustDslConfig();
boolean reload = dslYmlDto.hasRunProcess(ConsoleCommandOp.reload.name());
nodeProjectInfoModel.setCanReload(reload);
// 查询 dsl 流程信息
List<JSONObject> list = Arrays.stream(ConsoleCommandOp.values())
.filter(ConsoleCommandOp::isCanOpt)
.map(consoleCommandOp -> {
Tuple tuple = dslScriptServer.resolveProcessScript(nodeProjectInfoModel, dslYmlDto, consoleCommandOp);
JSONObject jsonObject = tuple.get(0);
jsonObject.put("process", consoleCommandOp);
return jsonObject;
})
.collect(Collectors.toList());
nodeProjectInfoModel.setDslProcessInfo(list);
}
}
return JsonMessage.success("", nodeProjectInfoModel);
}
@ -75,13 +107,8 @@ public class ProjectListController extends BaseAgentController {
*/
@RequestMapping(value = "getProjectInfo", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
public IJsonMessage<List<NodeProjectInfoModel>> getProjectInfo() {
try {
// 查询数据
List<NodeProjectInfoModel> nodeProjectInfoModels = projectInfoService.list();
return JsonMessage.success("查询成功!", nodeProjectInfoModels);
} catch (Exception e) {
log.error(e.getMessage(), e);
return new JsonMessage<>(500, "查询异常:" + e.getMessage());
}
// 查询数据
List<NodeProjectInfoModel> nodeProjectInfoModels = projectInfoService.list();
return JsonMessage.success("", nodeProjectInfoModels);
}
}

View File

@ -20,7 +20,7 @@
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package org.dromara.jpom.controller.manage.log;
package org.dromara.jpom.controller.manage;
import cn.hutool.core.io.FileUtil;
import cn.hutool.extra.servlet.ServletUtil;
@ -29,7 +29,7 @@ import cn.keepbx.jpom.model.JsonMessage;
import com.alibaba.fastjson2.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.dromara.jpom.common.BaseAgentController;
import org.dromara.jpom.common.commander.AbstractProjectCommander;
import org.dromara.jpom.common.commander.ProjectCommander;
import org.dromara.jpom.model.data.NodeProjectInfoModel;
import org.dromara.jpom.util.FileUtils;
import org.springframework.http.MediaType;
@ -50,7 +50,13 @@ import java.util.List;
@RestController
@RequestMapping(value = "manage/log")
@Slf4j
public class LogBackController extends BaseAgentController {
public class ProjectLogBackController extends BaseAgentController {
private final ProjectCommander projectCommander;
public ProjectLogBackController(ProjectCommander projectCommander) {
this.projectCommander = projectCommander;
}
@RequestMapping(value = "logSize", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
public IJsonMessage<JSONObject> logSize(String id) {
@ -58,19 +64,40 @@ public class LogBackController extends BaseAgentController {
JSONObject jsonObject = new JSONObject();
//
//获取日志备份路径
File logBack = nodeProjectInfoModel.getLogBack();
File logBack = projectInfoService.resolveLogBack(nodeProjectInfoModel);
boolean logBackBool = logBack.exists() && logBack.isDirectory();
jsonObject.put("logBack", logBackBool);
String info = projectInfoService.getLogSize(nodeProjectInfoModel);
String info = this.getLogSize(nodeProjectInfoModel);
jsonObject.put("logSize", info);
return JsonMessage.success("", jsonObject);
}
/**
* 查看项目控制台日志文件大小
*
* @param nodeProjectInfoModel 项目
* @return 文件大小
*/
private String getLogSize(NodeProjectInfoModel nodeProjectInfoModel) {
if (nodeProjectInfoModel == null) {
return null;
}
File file = projectInfoService.resolveAbsoluteLogFile(nodeProjectInfoModel);
if (file.exists()) {
long fileSize = file.length();
if (fileSize <= 0) {
return null;
}
return FileUtil.readableFileSize(fileSize);
}
return null;
}
@RequestMapping(value = "resetLog", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
public IJsonMessage<String> resetLog() {
NodeProjectInfoModel pim = getProjectInfoModel();
try {
String msg = AbstractProjectCommander.getInstance().backLog(pim);
String msg = projectCommander.backLog(pim);
if (msg.contains("ok")) {
return JsonMessage.success("重置成功");
}
@ -85,7 +112,7 @@ public class LogBackController extends BaseAgentController {
public IJsonMessage<String> clear(String name) {
Assert.hasText(name, "没有对应到文件");
NodeProjectInfoModel pim = getProjectInfoModel();
File logBack = pim.getLogBack();
File logBack = projectInfoService.resolveLogBack(pim);
if (logBack.exists() && logBack.isDirectory()) {
logBack = FileUtil.file(logBack, name);
if (logBack.exists()) {
@ -103,7 +130,7 @@ public class LogBackController extends BaseAgentController {
Assert.hasText(key, "请选择对应到文件");
try {
NodeProjectInfoModel pim = getProjectInfoModel();
File logBack = pim.getLogBack();
File logBack = projectInfoService.resolveLogBack(pim);
if (logBack.exists() && logBack.isDirectory()) {
logBack = FileUtil.file(logBack, key);
ServletUtil.write(response, logBack);
@ -122,8 +149,8 @@ public class LogBackController extends BaseAgentController {
NodeProjectInfoModel pim = getProjectInfoModel();
JSONObject jsonObject = new JSONObject();
File logBack = pim.getLogBack();
NodeProjectInfoModel infoModel = projectInfoService.resolveModel(pim);
File logBack = projectInfoService.resolveLogBack(pim, infoModel);
if (logBack.exists() && logBack.isDirectory()) {
File[] filesAll = logBack.listFiles();
if (filesAll != null) {
@ -132,17 +159,17 @@ public class LogBackController extends BaseAgentController {
}
}
jsonObject.put("id", pim.getId());
jsonObject.put("logPath", pim.getLog());
jsonObject.put("logPath", projectInfoService.resolveAbsoluteLog(pim, infoModel));
jsonObject.put("logBackPath", logBack.getAbsolutePath());
return JsonMessage.success("", jsonObject);
}
@RequestMapping(value = "export.html", method = RequestMethod.GET)
@RequestMapping(value = "export", method = RequestMethod.GET)
@ResponseBody
public void export(HttpServletResponse response) {
NodeProjectInfoModel pim = getProjectInfoModel();
File file = new File(pim.getLog());
File file = projectInfoService.resolveAbsoluteLogFile(pim);
if (!file.exists()) {
ServletUtil.write(response, JsonMessage.getString(400, "没有日志文件:" + file.getPath()), MediaType.APPLICATION_JSON_VALUE);
return;

View File

@ -22,6 +22,7 @@
*/
package org.dromara.jpom.controller.manage;
import cn.hutool.core.util.EnumUtil;
import cn.hutool.core.util.StrUtil;
import cn.keepbx.jpom.IJsonMessage;
import cn.keepbx.jpom.model.JsonMessage;
@ -29,12 +30,11 @@ import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.dromara.jpom.common.BaseAgentController;
import org.dromara.jpom.common.commander.AbstractProjectCommander;
import org.dromara.jpom.common.commander.CommandOpResult;
import org.dromara.jpom.common.commander.ProjectCommander;
import org.dromara.jpom.common.validator.ValidatorItem;
import org.dromara.jpom.common.validator.ValidatorRule;
import org.dromara.jpom.model.data.NodeProjectInfoModel;
import org.dromara.jpom.service.manage.ConsoleService;
import org.dromara.jpom.socket.ConsoleCommandOp;
import org.dromara.jpom.util.CommandUtil;
import org.springframework.http.MediaType;
@ -53,11 +53,10 @@ import org.springframework.web.bind.annotation.RestController;
@RequestMapping(value = "/manage/")
@Slf4j
public class ProjectStatusController extends BaseAgentController {
private final ProjectCommander projectCommander;
private final ConsoleService consoleService;
public ProjectStatusController(ConsoleService consoleService) {
this.consoleService = consoleService;
public ProjectStatusController(ProjectCommander projectCommander) {
this.projectCommander = projectCommander;
}
/**
@ -74,7 +73,7 @@ public class ProjectStatusController extends BaseAgentController {
try {
CommandUtil.openCache();
try {
CommandOpResult status = AbstractProjectCommander.getInstance().status(nodeProjectInfoModel);
CommandOpResult status = projectCommander.execCommand(ConsoleCommandOp.status, nodeProjectInfoModel);
jsonObject.put("pId", status.getPid());
jsonObject.put("pIds", status.getPids());
jsonObject.put("statusMsg", status.getStatusMsg());
@ -106,8 +105,8 @@ public class ProjectStatusController extends BaseAgentController {
try {
NodeProjectInfoModel projectInfoServiceItem = projectInfoService.getItem(item);
itemObj.put("name", projectInfoServiceItem.getName());
CommandOpResult commandOpResult = AbstractProjectCommander.getInstance().status(projectInfoServiceItem);
int pid = commandOpResult.getPid();
CommandOpResult commandOpResult = projectCommander.execCommand(ConsoleCommandOp.status, projectInfoServiceItem);
Integer pid = commandOpResult.getPid();
//
itemObj.put("pid", pid);
itemObj.put("pids", commandOpResult.getPids());
@ -115,7 +114,7 @@ public class ProjectStatusController extends BaseAgentController {
if (StrUtil.isNotEmpty(commandOpResult.getPorts())) {
itemObj.put("port", commandOpResult.getPorts());
} else {
String port = AbstractProjectCommander.getInstance().getMainPort(pid);
String port = projectCommander.getMainPort(pid);
itemObj.put("port", port);
}
} catch (Exception e) {
@ -131,49 +130,15 @@ public class ProjectStatusController extends BaseAgentController {
}
@RequestMapping(value = "restart", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
public IJsonMessage<CommandOpResult> restart(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "项目id 不正确") String id) {
@RequestMapping(value = "operate", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
public IJsonMessage<CommandOpResult> operate(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "项目id 不正确") String id,
@ValidatorItem String opt) throws Exception {
NodeProjectInfoModel item = projectInfoService.getItem(id);
Assert.notNull(item, "没有找到对应的项目");
try {
CommandOpResult result = consoleService.execCommand(ConsoleCommandOp.restart, item);
// boolean status = AbstractProjectCommander.getInstance().isRun(item, copyItem);
return new JsonMessage<>(result.isSuccess() ? 200 : 201, result.isSuccess() ? "操作成功" : "操作失败:" + result.msgStr(), result);
} catch (Exception e) {
log.error("重启项目异常", e);
return new JsonMessage<>(500, "重启项目异常:" + e.getMessage());
}
}
@RequestMapping(value = "stop", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
public IJsonMessage<CommandOpResult> stop(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "项目id 不正确") String id) {
NodeProjectInfoModel item = projectInfoService.getItem(id);
Assert.notNull(item, "没有找到对应的项目");
try {
CommandOpResult result = consoleService.execCommand(ConsoleCommandOp.stop, item);
return new JsonMessage<>(result.isSuccess() ? 200 : 201, result.isSuccess() ? "操作成功" : "操作失败:" + result.msgStr(), result);
} catch (Exception e) {
log.error("关闭项目异常", e);
return new JsonMessage<>(500, "关闭项目异常:" + e.getMessage());
}
}
@RequestMapping(value = "start", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
public IJsonMessage<CommandOpResult> start(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "项目id 不正确") String id) {
NodeProjectInfoModel item = projectInfoService.getItem(id);
Assert.notNull(item, "没有找到对应的项目");
try {
CommandOpResult result = consoleService.execCommand(ConsoleCommandOp.start, item);
return new JsonMessage<>(result.isSuccess() ? 200 : 201, result.isSuccess() ? "操作成功" : "操作失败:" + result.msgStr(), result);
} catch (Exception e) {
log.error("获取项目pid 失败", e);
return new JsonMessage<>(500, "启动项目异常:" + e.getMessage());
}
ConsoleCommandOp consoleCommandOp = EnumUtil.fromStringQuietly(ConsoleCommandOp.class, opt);
Assert.notNull(consoleCommandOp, "请选择操作类型");
Assert.state(consoleCommandOp.isCanOpt(), "不支持当前操作:" + opt);
CommandOpResult result = projectCommander.execCommand(consoleCommandOp, item);
return new JsonMessage<>(result.isSuccess() ? 200 : 201, result.isSuccess() ? "操作成功" : "操作失败:" + result.msgStr(), result);
}
}

View File

@ -157,8 +157,8 @@ public class ScriptController extends BaseAgentController {
*/
@RequestMapping(value = "log", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
public IJsonMessage<JSONObject> getNowLog(@ValidatorItem() String id,
@ValidatorItem() String executeId,
@ValidatorItem(value = ValidatorRule.POSITIVE_INTEGER, msg = "line") int line) {
@ValidatorItem() String executeId,
@ValidatorItem(value = ValidatorRule.POSITIVE_INTEGER, msg = "line") int line) {
NodeScriptModel item = nodeScriptServer.getItem(id);
Assert.notNull(item, "没有对应数据");
File logFile = item.logFile(executeId);
@ -179,7 +179,7 @@ public class ScriptController extends BaseAgentController {
*/
@RequestMapping(value = "del_log", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
public IJsonMessage<Object> delLog(@ValidatorItem() String id,
@ValidatorItem() String executeId) {
@ValidatorItem() String executeId) {
NodeScriptModel item = nodeScriptServer.getItem(id);
if (item == null) {
return JsonMessage.success("对应的脚本模版已经不存在拉");
@ -257,4 +257,18 @@ public class ScriptController extends BaseAgentController {
}
return JsonMessage.success("删除成功");
}
@RequestMapping(value = "change-workspace-id", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
public IJsonMessage<Object> changeWorkspaceId(@ValidatorItem() String id, String newWorkspaceId, String newNodeId) {
Assert.hasText(newWorkspaceId, "请选择要修改的工作空间");
Assert.hasText(newWorkspaceId, "请选择要修改的节");
NodeScriptModel item = nodeScriptServer.getItem(id);
Assert.notNull(item, "找不到对应的脚本信息");
//
NodeScriptModel update = new NodeScriptModel();
update.setNodeId(newNodeId);
update.setWorkspaceId(newWorkspaceId);
nodeScriptServer.updateById(update, item.getId());
return JsonMessage.success("修改成功");
}
}

View File

@ -37,6 +37,7 @@ import org.dromara.jpom.common.validator.ValidatorRule;
import org.dromara.jpom.cron.CronUtils;
import org.dromara.jpom.model.system.WorkspaceEnvVarModel;
import org.dromara.jpom.plugin.PluginFactory;
import org.dromara.jpom.service.script.NodeScriptExecLogServer;
import org.dromara.jpom.service.system.AgentWorkspaceEnvVarService;
import org.dromara.jpom.socket.AgentFileTailWatcher;
import org.dromara.jpom.util.CommandUtil;
@ -63,15 +64,18 @@ public class AgentCacheManageController extends BaseAgentController implements I
private final AgentWorkspaceEnvVarService agentWorkspaceEnvVarService;
private final JpomApplication configBean;
private final NodeScriptExecLogServer nodeScriptExecLogServer;
private long dataSize;
private long oldJarsSize;
private long tempFileSize;
public AgentCacheManageController(AgentWorkspaceEnvVarService agentWorkspaceEnvVarService,
JpomApplication configBean) {
JpomApplication configBean,
NodeScriptExecLogServer nodeScriptExecLogServer) {
this.agentWorkspaceEnvVarService = agentWorkspaceEnvVarService;
this.configBean = configBean;
this.nodeScriptExecLogServer = nodeScriptExecLogServer;
}
/**
@ -102,6 +106,9 @@ public class AgentCacheManageController extends BaseAgentController implements I
}
jsonObject.put("dateTime", DateTime.now().toString());
jsonObject.put("timeZoneId", TimeZone.getDefault().getID());
// 待同步待日志数
int size = nodeScriptExecLogServer.size();
jsonObject.put("scriptExecLogSize", size);
//
return JsonMessage.success("ok", jsonObject);
}

View File

@ -37,7 +37,7 @@ import org.dromara.jpom.common.BaseAgentController;
import org.dromara.jpom.common.Const;
import org.dromara.jpom.common.JpomManifest;
import org.dromara.jpom.common.RemoteVersion;
import org.dromara.jpom.system.AgentConfig;
import org.dromara.jpom.configuration.AgentConfig;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;

View File

@ -30,7 +30,6 @@ import cn.keepbx.jpom.model.JsonMessage;
import org.dromara.jpom.common.BaseJpomController;
import org.dromara.jpom.model.data.AgentWhitelist;
import org.dromara.jpom.service.WhitelistDirectoryService;
import org.dromara.jpom.system.AgentConfig;
import org.springframework.http.MediaType;
import org.springframework.util.Assert;
import org.springframework.web.bind.annotation.PostMapping;
@ -49,12 +48,9 @@ import java.util.List;
public class WhitelistDirectoryController extends BaseJpomController {
private final WhitelistDirectoryService whitelistDirectoryService;
private final AgentConfig agentConfig;
public WhitelistDirectoryController(WhitelistDirectoryService whitelistDirectoryService,
AgentConfig agentConfig) {
public WhitelistDirectoryController(WhitelistDirectoryService whitelistDirectoryService) {
this.whitelistDirectoryService = whitelistDirectoryService;
this.agentConfig = agentConfig;
}
@RequestMapping(value = "whitelistDirectory_data", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE)
@ -69,7 +65,7 @@ public class WhitelistDirectoryController extends BaseJpomController {
String allowEditSuffix) {
List<String> list = AgentWhitelist.parseToList(project, true, "项目路径白名单不能为空");
List<String> list = AgentWhitelist.parseToList(project, true, "项目路径授权不能为空");
//
List<String> allowEditSuffixList = AgentWhitelist.parseToList(allowEditSuffix, "允许编辑的文件后缀不能为空");
return save(list, allowEditSuffixList);
@ -81,9 +77,9 @@ public class WhitelistDirectoryController extends BaseJpomController {
List<String> allowEditSuffixList) {
List<String> projectArray;
{
projectArray = AgentWhitelist.covertToArray(projects, "项目路径白名单不能位于Jpom目录下");
String error = findStartsWith(projectArray, 0);
Assert.isNull(error, "白名单目录中不能存在包含关系:" + error);
projectArray = AgentWhitelist.covertToArray(projects, "项目路径授权不能位于Jpom目录下");
String error = findStartsWith(projectArray);
Assert.isNull(error, "授权目录中不能存在包含关系:" + error);
}
//
@ -110,30 +106,12 @@ public class WhitelistDirectoryController extends BaseJpomController {
}
/**
* 检查白名单包含关系
* 检查授权包含关系
*
* @param jsonArray 要检查的对象
* @param start 检查的坐标
* @return null 正常
*/
private String findStartsWith(List<String> jsonArray, int start) {
if (jsonArray == null || !agentConfig.getWhitelist().isCheckStartsWith()) {
return null;
}
String str = jsonArray.get(start);
int len = jsonArray.size();
for (int i = 0; i < len; i++) {
if (i == start) {
continue;
}
String findStr = jsonArray.get(i);
if (findStr.startsWith(str)) {
return str;
}
}
if (start < len - 1) {
return findStartsWith(jsonArray, start + 1);
}
return null;
private String findStartsWith(List<String> jsonArray) {
return AgentWhitelist.findStartsWith(jsonArray);
}
}

View File

@ -23,12 +23,10 @@
package org.dromara.jpom.model.data;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.setting.yaml.YamlUtil;
import cn.keepbx.jpom.model.BaseJsonModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.util.Assert;
import java.io.ByteArrayInputStream;
import java.io.InputStream;
@ -63,14 +61,12 @@ public class DslYmlDto extends BaseJsonModel {
*/
private Config config;
public DslYmlDto.BaseProcess runProcess(String opt) {
DslYmlDto.Run run = this.getRun();
Assert.notNull(run, "yml 未配置 运行管理");
DslYmlDto.BaseProcess baseProcess = (DslYmlDto.BaseProcess) ReflectUtil.getFieldValue(run, opt);
Assert.notNull(baseProcess, "未找到对应的类型或者未配置 " + opt);
return baseProcess;
}
/**
* 判断是否包含指定流程
*
* @param opt 流程名
* @return true
*/
public boolean hasRunProcess(String opt) {
DslYmlDto.Run run = this.getRun();
if (run == null) {
@ -101,11 +97,26 @@ public class DslYmlDto extends BaseJsonModel {
private Status status;
private Stop stop;
private Restart restart;
private Reload reload;
/**
* 文件变动是否执行重新加载
*/
private Boolean fileChangeReload;
}
/**
* 重新加载
*
* @see org.dromara.jpom.socket.ConsoleCommandOp
*/
public static class Reload extends BaseProcess {
}
/**
* 启动流程
*
* @see org.dromara.jpom.socket.ConsoleCommandOp
*/
public static class Start extends BaseProcess {
@ -113,6 +124,8 @@ public class DslYmlDto extends BaseJsonModel {
/**
* 获取状态流程
*
* @see org.dromara.jpom.socket.ConsoleCommandOp
*/
public static class Status extends BaseProcess {
@ -120,6 +133,8 @@ public class DslYmlDto extends BaseJsonModel {
/**
* 停止流程
*
* @see org.dromara.jpom.socket.ConsoleCommandOp
*/
public static class Stop extends BaseProcess {
@ -127,6 +142,8 @@ public class DslYmlDto extends BaseJsonModel {
/**
* 重启流程
*
* @see org.dromara.jpom.socket.ConsoleCommandOp
*/
public static class Restart extends BaseProcess {
@ -147,15 +164,6 @@ public class DslYmlDto extends BaseJsonModel {
* 执行脚本的环境变量
*/
private Map<String, String> scriptEnv;
/**
* 通过 脚本模版运行
*
* @return true
*/
public boolean runByScript() {
return StrUtil.isNotEmpty(this.getScriptId());
}
}
@Data

View File

@ -22,23 +22,19 @@
*/
package org.dromara.jpom.model.data;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ReflectUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSONObject;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.dromara.jpom.common.commander.CommandOpResult;
import org.dromara.jpom.model.RunMode;
import org.dromara.jpom.system.JpomRuntimeException;
import org.springframework.util.Assert;
import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
/**
* 项目配置信息实体
@ -52,18 +48,22 @@ public class NodeProjectInfoModel extends BaseWorkspaceModel {
* 分组
*/
private String group;
private String mainClass;
/**
* 项目路径
*/
private String lib;
/**
* 白名单目录
* 授权目录
*/
private String whitelistDirectory;
private String log;
/**
* 日志目录
*/
private String logPath;
/**
* java 模式运行的 class
*/
private String mainClass;
/**
* jvm 参数
*/
@ -76,24 +76,23 @@ public class NodeProjectInfoModel extends BaseWorkspaceModel {
* WebHooks
*/
private String token;
/**
* 项目运行模式
*/
private RunMode runMode;
/**
* 软链的父级项目id
*/
private String linkId;
/**
* 节点分发项目不允许在项目管理中编辑
*/
private boolean outGivingProject;
/**
* 实际运行的命令
*/
private String runCommand;
private Boolean outGivingProject;
/**
* -Djava.ext.dirs=lib -cp conf:run.jar
* 填写lib:conf
*/
private String javaExtDirsCp;
/**
* 项目自动启动
*/
@ -108,160 +107,60 @@ public class NodeProjectInfoModel extends BaseWorkspaceModel {
* dsl 环境变量
*/
private String dslEnv;
/**
* 最后一次执行 reload 结果
*/
private CommandOpResult lastReloadResult;
// ---------------- 中转字段 start
/**
* 是否可以重新加载
*/
private Boolean canReload;
/**
* DSL 流程信息统计
*/
private List<JSONObject> dslProcessInfo;
/**
* 实际运行的命令
*/
private String runCommand;
// ---------------- 中转字段 end
public String getJavaExtDirsCp() {
public String javaExtDirsCp() {
return StrUtil.emptyToDefault(javaExtDirsCp, StrUtil.EMPTY);
}
public boolean isOutGivingProject() {
return outGivingProject;
return outGivingProject != null && outGivingProject;
}
public void setOutGivingProject(boolean outGivingProject) {
this.outGivingProject = outGivingProject;
}
public RunMode getRunMode() {
if (runMode == null) {
return RunMode.ClassPath;
}
return runMode;
}
public String getMainClass() {
public String mainClass() {
return StrUtil.emptyToDefault(mainClass, StrUtil.EMPTY);
}
public String getWhitelistDirectory() {
public String whitelistDirectory() {
if (StrUtil.isEmpty(whitelistDirectory)) {
throw new JpomRuntimeException("恢复白名单数据异常");
throw new JpomRuntimeException("恢复授权数据异常或者没有选择授权目录");
}
return whitelistDirectory;
}
public void setWhitelistDirectory(String whitelistDirectory) {
this.whitelistDirectory = whitelistDirectory;
}
public String allLib() {
String directory = this.getWhitelistDirectory();
directory = AgentWhitelist.convertRealPath(directory);
String directory = this.whitelistDirectory();
return FileUtil.file(directory, this.getLib()).getAbsolutePath();
}
/**
* 获取项目文件中的所有jar 文件
*
* @param nodeProjectInfoModel 项目
* @return list
*/
public static List<File> listJars(NodeProjectInfoModel nodeProjectInfoModel) {
File fileLib = new File(nodeProjectInfoModel.allLib());
File[] files = fileLib.listFiles();
if (files == null) {
return new ArrayList<>();
}
RunMode runMode = nodeProjectInfoModel.getRunMode();
return Arrays.stream(files)
.filter(File::isFile)
.filter(file -> {
if (runMode == RunMode.ClassPath || runMode == RunMode.Jar || runMode == RunMode.JavaExtDirsCp) {
return StrUtil.endWith(file.getName(), FileUtil.JAR_FILE_EXT, true);
} else if (runMode == RunMode.JarWar) {
return StrUtil.endWith(file.getName(), "war", true);
}
return false;
})
.collect(Collectors.toList());
}
/**
* 拼接java 执行的jar路径
*
* @param nodeProjectInfoModel 项目
* @return classpath 或者 jar
*/
public static String getClassPathLib(NodeProjectInfoModel nodeProjectInfoModel) {
List<File> files = listJars(nodeProjectInfoModel);
if (CollUtil.isEmpty(files)) {
return "";
}
// 获取lib下面的所有jar包
StringBuilder classPath = new StringBuilder();
RunMode runMode = nodeProjectInfoModel.getRunMode();
int len = files.size();
if (runMode == RunMode.ClassPath) {
classPath.append("-classpath ");
} else if (runMode == RunMode.Jar || runMode == RunMode.JarWar) {
classPath.append("-jar ");
// 只取一个jar文件
len = 1;
} else if (runMode == RunMode.JavaExtDirsCp) {
classPath.append("-Djava.ext.dirs=");
String javaExtDirsCp = nodeProjectInfoModel.getJavaExtDirsCp();
String[] split = StrUtil.splitToArray(javaExtDirsCp, StrUtil.COLON);
if (ArrayUtil.isEmpty(split)) {
classPath.append(". -cp ");
} else {
classPath.append(split[0]).append(" -cp ");
if (split.length > 1) {
classPath.append(split[1]).append(FileUtil.PATH_SEPARATOR);
}
}
} else {
return StrUtil.EMPTY;
}
for (int i = 0; i < len; i++) {
File file = files.get(i);
classPath.append(file.getAbsolutePath());
if (i != len - 1) {
classPath.append(FileUtil.PATH_SEPARATOR);
}
}
return classPath.toString();
}
public String getLogPath() {
public String logPath() {
return StrUtil.emptyToDefault(this.logPath, StrUtil.EMPTY);
}
public String getLog() {
if (StrUtil.isEmpty(this.getId())) {
return StrUtil.EMPTY;
}
if (StrUtil.isNotEmpty(this.getLogPath())) {
return FileUtil.normalize(String.format("%s/%s/%s.log", this.getLogPath(), this.getId(), this.getId()));
}
if (StrUtil.isEmpty(this.log)) {
String log = new File(this.allLib()).getParent();
this.log = FileUtil.normalize(String.format("%s/%s.log", log, this.getId()));
}
return StrUtil.emptyToDefault(this.log, StrUtil.EMPTY);
}
public String getAbsoluteLog() {
String pathname = getLog();
Assert.hasText(pathname, "log path error");
File file = new File(pathname);
// auto create dir
FileUtil.mkParentDirs(file);
return file.getAbsolutePath();
}
public File getLogBack() {
String log1 = getLog();
Assert.hasText(log1, "log path error");
return new File(log1 + "_back");
}
/**
* 默认
*
* @return url token
*/
public String getToken() {
public String token() {
// 兼容旧数据
if ("no".equalsIgnoreCase(this.token)) {
return "";
@ -269,6 +168,11 @@ public class NodeProjectInfoModel extends BaseWorkspaceModel {
return StrUtil.emptyToDefault(token, StrUtil.EMPTY);
}
/**
* 获取当前 dsl 配置
*
* @return DslYmlDto
*/
public DslYmlDto dslConfig() {
String dslContent = this.getDslContent();
if (StrUtil.isEmpty(dslContent)) {
@ -277,6 +181,17 @@ public class NodeProjectInfoModel extends BaseWorkspaceModel {
return DslYmlDto.build(dslContent);
}
/**
* 必须存在 dsl 配置
*
* @return DslYmlDto
*/
public DslYmlDto mustDslConfig() {
DslYmlDto dslYmlDto = this.dslConfig();
Assert.notNull(dslYmlDto, "未配置 dsl 信息(项目信息错误)");
return dslYmlDto;
}
/**
* 获取 dsl 流程信息
*
@ -285,6 +200,16 @@ public class NodeProjectInfoModel extends BaseWorkspaceModel {
*/
public DslYmlDto.BaseProcess tryDslProcess(String opt) {
DslYmlDto build = dslConfig();
return tryDslProcess(build, opt);
}
/**
* 获取 dsl 流程信息
*
* @param opt 操作
* @return 结果
*/
public static DslYmlDto.BaseProcess tryDslProcess(DslYmlDto build, String opt) {
return Optional.ofNullable(build)
.map(DslYmlDto::getRun)
.map(run -> (DslYmlDto.BaseProcess) ReflectUtil.getFieldValue(run, opt))
@ -298,8 +223,8 @@ public class NodeProjectInfoModel extends BaseWorkspaceModel {
* @return 结果
*/
public DslYmlDto.BaseProcess getDslProcess(String opt) {
DslYmlDto build = dslConfig();
Assert.notNull(build, "yml 还未配置");
return build.runProcess(opt);
DslYmlDto.BaseProcess baseProcess = this.tryDslProcess(opt);
Assert.notNull(baseProcess, "DSL 未配置运行管理或者未配置 " + opt + " 流程");
return baseProcess;
}
}

View File

@ -24,6 +24,8 @@ package org.dromara.jpom.model.system;
import cn.hutool.core.util.StrUtil;
import cn.keepbx.jpom.model.BaseJsonModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
/**
* 网络端口信息实体
@ -31,74 +33,20 @@ import cn.keepbx.jpom.model.BaseJsonModel;
* @author bwcx_jzy
* @since 2019/4/10
*/
@EqualsAndHashCode(callSuper = true)
@Data
public class NetstatModel extends BaseJsonModel {
/**
* 协议
*/
private String protocol;
private String receive = StrUtil.DASHED;
private String send = StrUtil.DASHED;
/**
* 端口
*/
private String local;
private String foreign;
private String status;
private String name;
public String getProtocol() {
return protocol;
}
public void setProtocol(String protocol) {
this.protocol = protocol;
}
public String getReceive() {
return receive;
}
public void setReceive(String receive) {
this.receive = receive;
}
public String getSend() {
return send;
}
public void setSend(String send) {
this.send = send;
}
public String getLocal() {
return local;
}
public void setLocal(String local) {
this.local = local;
}
public String getForeign() {
return foreign;
}
public void setForeign(String foreign) {
this.foreign = foreign;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
/**
* 协议
*/
private String protocol;
private String receive = StrUtil.DASHED;
private String send = StrUtil.DASHED;
/**
* 端口
*/
private String local;
private String foreign;
private String status;
private String name;
}

View File

@ -28,36 +28,18 @@ import cn.hutool.core.date.DateTime;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.LineHandler;
import cn.hutool.core.lang.Opt;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.net.url.UrlQuery;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.lang.Tuple;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.dromara.jpom.JpomApplication;
import org.dromara.jpom.common.Const;
import org.dromara.jpom.common.IllegalArgument2Exception;
import org.dromara.jpom.model.EnvironmentMapBuilder;
import org.dromara.jpom.model.data.DslYmlDto;
import org.dromara.jpom.model.data.NodeProjectInfoModel;
import org.dromara.jpom.model.data.NodeScriptModel;
import org.dromara.jpom.service.script.NodeScriptServer;
import org.dromara.jpom.service.system.AgentWorkspaceEnvVarService;
import org.dromara.jpom.system.AgentConfig;
import org.dromara.jpom.system.ExtConfigBean;
import org.dromara.jpom.util.CommandUtil;
import org.dromara.jpom.util.FileUtils;
import java.io.File;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.Future;
/**
* dsl 执行脚本
@ -76,11 +58,11 @@ public class DslScriptBuilder extends BaseRunScript implements Runnable {
private boolean autoDelete;
private EnvironmentMapBuilder environmentMapBuilder;
private DslScriptBuilder(String action,
EnvironmentMapBuilder environmentMapBuilder,
String args,
String log,
Charset charset) {
public DslScriptBuilder(String action,
EnvironmentMapBuilder environmentMapBuilder,
String args,
String log,
Charset charset) {
super(FileUtil.file(log), charset);
this.action = action;
this.environmentMapBuilder = environmentMapBuilder;
@ -139,10 +121,14 @@ public class DslScriptBuilder extends BaseRunScript implements Runnable {
/**
* 执行
* <p>
* 0 退出码
* 1 日志
*/
public List<String> syncExecute() {
public Tuple syncExecute() {
ProcessBuilder processBuilder = this.init();
List<String> result = new ArrayList<>();
int waitFor = -100;
try {
//
process = processBuilder.start();
@ -150,18 +136,17 @@ public class DslScriptBuilder extends BaseRunScript implements Runnable {
IoUtil.readLines(inputStream, ExtConfigBean.getConsoleLogCharset(), (LineHandler) line -> result.add(this.formatLine(line)));
//
int waitFor = process.waitFor();
waitFor = process.waitFor();
// 插入第一行
result.add(0, this.formatLine(StrUtil.format("执行结束: {}", waitFor)));
result.add(0, this.formatLine(StrUtil.format("本次执行退出码: {}", waitFor)));
//
return result;
} catch (Exception e) {
log.error("执行异常", e);
result.add(this.formatLine(StrUtil.format("执行异常:", e.getMessage())));
} finally {
this.close();
}
return result;
return new Tuple(waitFor, result);
}
@Override
@ -186,94 +171,5 @@ public class DslScriptBuilder extends BaseRunScript implements Runnable {
}
}
/**
* 异步执行
*
* @param scriptProcess 脚本流程
* @param log 日志
*/
public static void run(DslYmlDto.BaseProcess scriptProcess, NodeProjectInfoModel nodeProjectInfoModel, String action, String log, boolean sync) throws Exception {
DslScriptBuilder builder = DslScriptBuilder.create(scriptProcess, nodeProjectInfoModel, action, log);
Future<?> execute = ThreadUtil.execAsync(builder);
if (sync) {
execute.get();
}
}
/**
* 同步执行
*
* @param scriptProcess 脚本流程
*/
public static List<String> syncRun(DslYmlDto.BaseProcess scriptProcess, NodeProjectInfoModel nodeProjectInfoModel, String action) {
try (DslScriptBuilder builder = DslScriptBuilder.create(scriptProcess, nodeProjectInfoModel, action, null)) {
return builder.syncExecute();
}
}
private static DslScriptBuilder create(DslYmlDto.BaseProcess scriptProcess, NodeProjectInfoModel nodeProjectInfoModel, String action, String log) {
NodeScriptServer nodeScriptServer = SpringUtil.getBean(NodeScriptServer.class);
AgentConfig agentConfig = SpringUtil.getBean(AgentConfig.class);
AgentConfig.ProjectConfig.LogConfig logConfig = agentConfig.getProject().getLog();
String scriptId = scriptProcess.getScriptId();
cn.hutool.core.lang.Assert.notBlank(scriptId, () -> new IllegalArgument2Exception("请填写脚本模板id"));
NodeScriptModel item = nodeScriptServer.getItem(scriptId);
EnvironmentMapBuilder environment = DslScriptBuilder.environment(nodeProjectInfoModel, scriptProcess);
File scriptFile;
boolean autoDelete = false;
if (item == null) {
scriptFile = FileUtil.file(nodeProjectInfoModel.allLib(), scriptId);
cn.hutool.core.lang.Assert.isTrue(FileUtil.isFile(scriptFile), () -> new IllegalArgument2Exception("脚本模版不存在:" + scriptProcess.getScriptId()));
} else {
scriptFile = DslScriptBuilder.initScriptFile(item);
// 系统生成的脚本需要自动删除
autoDelete = true;
}
DslScriptBuilder builder = new DslScriptBuilder(action, environment, scriptProcess.getScriptArgs(), log, logConfig.getFileCharset());
builder.setScriptFile(scriptFile);
builder.setAutoDelete(autoDelete);
return builder;
}
/**
* 创建脚本文件
*
* @param scriptModel 脚本对象
* @return file
*/
private static File initScriptFile(NodeScriptModel scriptModel) {
String dataPath = JpomApplication.getInstance().getDataPath();
File scriptFile = FileUtil.file(dataPath, Const.SCRIPT_RUN_CACHE_DIRECTORY, StrUtil.format("{}.{}", IdUtil.fastSimpleUUID(), CommandUtil.SUFFIX));
// 替换内容
String context = scriptModel.getContext();
FileUtils.writeScript(context, scriptFile, ExtConfigBean.getConsoleLogCharset());
return scriptFile;
}
private static EnvironmentMapBuilder environment(NodeProjectInfoModel nodeProjectInfoModel, DslYmlDto.BaseProcess scriptProcess) {
//
AgentWorkspaceEnvVarService workspaceService = SpringUtil.getBean(AgentWorkspaceEnvVarService.class);
EnvironmentMapBuilder environmentMapBuilder = workspaceService.getEnv(nodeProjectInfoModel.getWorkspaceId());
// 项目配置的环境变量
String dslEnv = nodeProjectInfoModel.getDslEnv();
Opt.ofBlankAble(dslEnv)
.map(s -> UrlQuery.of(s, CharsetUtil.CHARSET_UTF_8))
.map(UrlQuery::getQueryMap)
.map(map -> {
Map<String, String> map1 = MapUtil.newHashMap();
for (Map.Entry<CharSequence, CharSequence> entry : map.entrySet()) {
map1.put(StrUtil.toString(entry.getKey()), StrUtil.toString(entry.getValue()));
}
return map1;
})
.ifPresent(environmentMapBuilder::putStr);
//
environmentMapBuilder
.putStr(scriptProcess.getScriptEnv())
.put("PROJECT_ID", nodeProjectInfoModel.getId())
.put("PROJECT_NAME", nodeProjectInfoModel.getName())
.put("PROJECT_PATH", nodeProjectInfoModel.allLib());
return environmentMapBuilder;
}
}

View File

@ -1,147 +0,0 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2019 Code Technology Studio
*
* 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:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* 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.
*/
package org.dromara.jpom.service;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSONObject;
import org.dromara.jpom.JpomApplication;
import org.dromara.jpom.model.BaseModel;
import org.dromara.jpom.system.JpomRuntimeException;
import org.dromara.jpom.util.JsonFileUtil;
import java.io.FileNotFoundException;
/**
* 公共文件操作Service
*
* @author bwcx_jzy
* @since 2019/1/16
*/
public abstract class BaseDataService {
/**
* 获取数据文件的路径如果文件不存在则创建一个
*
* @param filename 文件名
* @return path
*/
protected String getDataFilePath(String filename) {
return FileUtil.normalize(JpomApplication.getInstance().getDataPath() + StrUtil.SLASH + filename);
}
/**
* 保存json对象
*
* @param filename 文件名
* @param json json数据
*/
protected void saveJson(String filename, BaseModel json) {
String key = json.getId();
// 读取文件如果存在记录则抛出异常
JSONObject allData;
JSONObject data = null;
allData = getJSONObject(filename);
if (allData != null) {
data = allData.getJSONObject(key);
} else {
allData = new JSONObject();
}
// 判断是否存在数据
if (null != data && 0 < data.keySet().size()) {
throw new JpomRuntimeException("数据Id已经存在啦" + filename + " :" + key);
} else {
allData.put(key, json.toJson());
JsonFileUtil.saveJson(getDataFilePath(filename), allData);
}
}
/**
* 修改json对象
*
* @param filename 文件名
* @param json json数据
*/
protected void updateJson(String filename, BaseModel json) {
String key = json.getId();
// 读取文件如果不存在记录则抛出异常
JSONObject allData = getJSONObject(filename);
JSONObject data = allData.getJSONObject(key);
// 判断是否存在数据
if (null == data || 0 == data.keySet().size()) {
throw new JpomRuntimeException("数据不存在:" + key);
} else {
allData.put(key, json.toJson());
JsonFileUtil.saveJson(getDataFilePath(filename), allData);
}
}
/**
* 删除json对象
*
* @param filename 文件
* @param key key
*/
protected void deleteJson(String filename, String key) {
// 读取文件如果存在记录则抛出异常
JSONObject allData = getJSONObject(filename);
if (allData == null) {
return;
}
//Assert.notNull(allData, "没有任何数据");
//JSONObject data = allData.getJSONObject(key);
allData.remove(key);
JsonFileUtil.saveJson(getDataFilePath(filename), allData);
}
/**
* 读取整个json文件
*
* @param filename 文件名
* @return json
*/
protected JSONObject getJSONObject(String filename) {
try {
return (JSONObject) JsonFileUtil.readJson(getDataFilePath(filename));
} catch (FileNotFoundException e) {
return null;
}
}
protected <T> T getJsonObjectById(String file, String id, Class<T> cls) {
if (StrUtil.isEmpty(id)) {
return null;
}
JSONObject jsonObject = getJSONObject(file);
if (jsonObject == null) {
return null;
}
jsonObject = jsonObject.getJSONObject(id);
if (jsonObject == null) {
return null;
}
return jsonObject.toJavaObject(cls);
}
}

View File

@ -22,15 +22,27 @@
*/
package org.dromara.jpom.service;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.bean.copier.CopyOptions;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.thread.lock.LockUtil;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import org.dromara.jpom.JpomApplication;
import org.dromara.jpom.model.BaseModel;
import org.dromara.jpom.system.JpomRuntimeException;
import org.dromara.jpom.util.JsonFileUtil;
import org.springframework.util.Assert;
import java.io.FileNotFoundException;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.locks.Lock;
/**
* 标准操作Service
@ -38,14 +50,15 @@ import java.util.Objects;
* @author bwcx_jzy
* @since 2019/3/14
*/
public abstract class BaseOperService<T extends BaseModel> extends BaseDataService {
public abstract class BaseOperService<T extends BaseModel> {
private final String fileName;
private final Class<?> typeArgument;
private final Class<T> typeArgument;
private final Lock lock = LockUtil.createStampLock().asWriteLock();
public BaseOperService(String fileName) {
this.fileName = fileName;
this.typeArgument = ClassUtil.getTypeArgument(this.getClass());
this.typeArgument = (Class<T>) ClassUtil.getTypeArgument(this.getClass());
}
/**
@ -54,7 +67,12 @@ public abstract class BaseOperService<T extends BaseModel> extends BaseDataServi
* @return list
*/
public List<T> list() {
return (List<T>) list(typeArgument);
return list(typeArgument);
}
public int size() {
List<T> list = this.list();
return CollUtil.size(list);
}
public <E> List<E> list(Class<E> cls) {
@ -79,7 +97,7 @@ public abstract class BaseOperService<T extends BaseModel> extends BaseDataServi
*/
public T getItem(String id) {
Objects.requireNonNull(fileName, "没有配置fileName");
return (T) getJsonObjectById(fileName, id, typeArgument);
return getJsonObjectById(fileName, id, typeArgument);
}
@ -90,7 +108,12 @@ public abstract class BaseOperService<T extends BaseModel> extends BaseDataServi
*/
public void addItem(T t) {
Objects.requireNonNull(fileName, "没有配置fileName");
saveJson(fileName, t);
try {
lock.lock();
saveJson(fileName, t);
} finally {
lock.unlock();
}
}
/**
@ -100,7 +123,12 @@ public abstract class BaseOperService<T extends BaseModel> extends BaseDataServi
*/
public void deleteItem(String id) {
Objects.requireNonNull(fileName, "没有配置fileName");
deleteJson(fileName, id);
try {
lock.lock();
deleteJson(fileName, id);
} finally {
lock.unlock();
}
}
/**
@ -110,8 +138,131 @@ public abstract class BaseOperService<T extends BaseModel> extends BaseDataServi
*/
public void updateItem(T t) {
Objects.requireNonNull(fileName, "没有配置fileName");
updateJson(fileName, t);
try {
lock.lock();
updateJson(fileName, t);
} finally {
lock.unlock();
}
}
/**
* 根据数据Id 修改
*
* @param updateData 实体
* @param id 数据Id
*/
public void updateById(T updateData, String id) {
Objects.requireNonNull(fileName, "没有配置fileName");
try {
lock.lock();
T item = getItem(id);
Assert.notNull(item, "数据不存在");
BeanUtil.copyProperties(updateData, item, CopyOptions.create().ignoreNullValue());
updateJson(fileName, item);
} finally {
lock.unlock();
}
}
/**
* 获取数据文件的路径如果文件不存在则创建一个
*
* @param filename 文件名
* @return path
*/
protected String getDataFilePath(String filename) {
return FileUtil.normalize(JpomApplication.getInstance().getDataPath() + StrUtil.SLASH + filename);
}
/**
* 保存json对象
*
* @param filename 文件名
* @param json json数据
*/
protected void saveJson(String filename, BaseModel json) {
String key = json.getId();
// 读取文件如果存在记录则抛出异常
JSONObject allData = getJSONObject(filename);
if (allData != null) {
// 判断是否存在数据
if (allData.containsKey(key)) {
throw new JpomRuntimeException("数据Id已经存在啦" + filename + " :" + key);
}
} else {
allData = new JSONObject();
}
allData.put(key, json.toJson());
JsonFileUtil.saveJson(getDataFilePath(filename), allData);
}
/**
* 修改json对象
*
* @param filename 文件名
* @param json json数据
*/
protected void updateJson(String filename, BaseModel json) {
String key = json.getId();
// 读取文件如果不存在记录则抛出异常
JSONObject allData = getJSONObject(filename);
JSONObject data = allData.getJSONObject(key);
// 判断是否存在数据
if (MapUtil.isEmpty(data)) {
throw new JpomRuntimeException("数据不存在:" + key);
} else {
allData.put(key, json.toJson());
JsonFileUtil.saveJson(getDataFilePath(filename), allData);
}
}
/**
* 删除json对象
*
* @param filename 文件
* @param key key
*/
protected void deleteJson(String filename, String key) {
// 读取文件如果存在记录则抛出异常
JSONObject allData = getJSONObject(filename);
if (allData == null) {
return;
}
//Assert.notNull(allData, "没有任何数据");
//JSONObject data = allData.getJSONObject(key);
allData.remove(key);
JsonFileUtil.saveJson(getDataFilePath(filename), allData);
}
/**
* 读取整个json文件
*
* @param filename 文件名
* @return json
*/
protected JSONObject getJSONObject(String filename) {
try {
return (JSONObject) JsonFileUtil.readJson(getDataFilePath(filename));
} catch (FileNotFoundException e) {
return null;
}
}
protected T getJsonObjectById(String file, String id, Class<T> cls) {
if (StrUtil.isEmpty(id)) {
return null;
}
JSONObject jsonObject = getJSONObject(file);
if (jsonObject == null) {
return null;
}
jsonObject = jsonObject.getJSONObject(id);
if (jsonObject == null) {
return null;
}
return jsonObject.toJavaObject(cls);
}
}

View File

@ -61,4 +61,10 @@ public abstract class BaseWorkspaceOptService<T extends BaseWorkspaceModel> exte
}
super.updateItem(data);
}
@Override
public void updateById(T updateData, String id) {
updateData.setModifyTime(DateUtil.now());
super.updateById(updateData, id);
}
}

View File

@ -20,7 +20,7 @@
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package org.dromara.jpom.script;
package org.dromara.jpom.service;
import cn.hutool.core.collection.CollStreamUtil;
import cn.hutool.core.collection.CollUtil;
@ -32,15 +32,17 @@ import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.SecureUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.alibaba.fastjson2.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.dromara.jpom.JpomApplication;
import org.dromara.jpom.configuration.AgentConfig;
import org.dromara.jpom.configuration.ProjectConfig;
import org.dromara.jpom.model.data.DslYmlDto;
import org.dromara.jpom.model.data.NodeProjectInfoModel;
import org.dromara.jpom.system.AgentConfig;
import org.dromara.jpom.service.manage.ProjectInfoService;
import org.dromara.jpom.util.CommandUtil;
import org.dromara.jpom.util.StringUtil;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import java.io.File;
@ -54,7 +56,17 @@ import java.util.stream.Collectors;
* @since 2022/5/10
*/
@Slf4j
public class ProjectFileBackupUtil {
@Service
public class ProjectFileBackupService {
private final ProjectConfig projectConfig;
private final ProjectInfoService projectInfoService;
public ProjectFileBackupService(AgentConfig agentConfig,
ProjectInfoService projectInfoService) {
this.projectConfig = agentConfig.getProject();
this.projectInfoService = projectInfoService;
}
/**
* 整个项目的备份目录
@ -62,10 +74,11 @@ public class ProjectFileBackupUtil {
* @param projectInfoModel 项目
* @return file
*/
public static File pathProject(NodeProjectInfoModel projectInfoModel) {
DslYmlDto dslYmlDto = projectInfoModel.dslConfig();
public File pathProject(NodeProjectInfoModel projectInfoModel) {
NodeProjectInfoModel infoModel = projectInfoService.resolveModel(projectInfoModel);
DslYmlDto dslYmlDto = infoModel.dslConfig();
String backupPath = resolveBackupPath(dslYmlDto);
return pathProject(backupPath, projectInfoModel.getId());
return pathProject(backupPath, infoModel.getId());
}
/**
@ -75,7 +88,7 @@ public class ProjectFileBackupUtil {
* @param backupPath 备份路径
* @return file
*/
private static File pathProject(String backupPath, String pathId) {
private File pathProject(String backupPath, String pathId) {
if (StrUtil.isEmpty(backupPath)) {
String dataPath = JpomApplication.getInstance().getDataPath();
return FileUtil.file(dataPath, "project_file_backup", pathId);
@ -90,7 +103,7 @@ public class ProjectFileBackupUtil {
* @param backupId 备份ID
* @return file
*/
public static File pathProjectBackup(NodeProjectInfoModel projectInfoModel, String backupId) {
public File pathProjectBackup(NodeProjectInfoModel projectInfoModel, String backupId) {
File fileBackup = pathProject(projectInfoModel);
return FileUtil.file(fileBackup, backupId);
}
@ -100,19 +113,20 @@ public class ProjectFileBackupUtil {
*
* @param projectInfoModel 项目
*/
public static String backup(NodeProjectInfoModel projectInfoModel) {
int backupCount = resolveBackupCount(projectInfoModel.dslConfig());
public String backup(NodeProjectInfoModel projectInfoModel) {
NodeProjectInfoModel infoModel = projectInfoService.resolveModel(projectInfoModel);
int backupCount = resolveBackupCount(infoModel.dslConfig());
if (backupCount <= 0) {
// 未开启备份
return null;
}
File file = FileUtil.file(projectInfoModel.allLib());
File file = projectInfoService.resolveLibFile(infoModel);
//
if (!FileUtil.exist(file)) {
return null;
}
String backupId = DateTime.now().toString(DatePattern.PURE_DATETIME_MS_FORMAT);
File projectFileBackup = ProjectFileBackupUtil.pathProjectBackup(projectInfoModel, backupId);
File projectFileBackup = this.pathProjectBackup(infoModel, backupId);
Assert.state(!FileUtil.exist(projectFileBackup), "备份目录冲突:" + projectFileBackup.getName());
FileUtil.copyContent(file, projectFileBackup, true);
//
@ -124,7 +138,7 @@ public class ProjectFileBackupUtil {
*
* @param backupPath 目录
*/
private static void clearOldBackup(File backupPath, DslYmlDto dslYmlDto) {
private void clearOldBackup(File backupPath, DslYmlDto dslYmlDto) {
int backupCount = resolveBackupCount(dslYmlDto);
//
if (!FileUtil.isDirectory(backupPath)) {
@ -147,7 +161,13 @@ public class ProjectFileBackupUtil {
}
}
public static String resolveBackupPath(DslYmlDto dslYmlDto) {
/**
* 解析项目的备份路径
*
* @param dslYmlDto dsl 配置
* @return path
*/
public String resolveBackupPath(DslYmlDto dslYmlDto) {
return Optional.ofNullable(dslYmlDto)
.map(DslYmlDto::getFile)
.map(DslYmlDto.FileConfig::getBackupPath)
@ -155,15 +175,11 @@ public class ProjectFileBackupUtil {
.orElse(null);
}
public static int resolveBackupCount(DslYmlDto dslYmlDto) {
public int resolveBackupCount(DslYmlDto dslYmlDto) {
return Optional.ofNullable(dslYmlDto)
.map(DslYmlDto::getFile)
.map(DslYmlDto.FileConfig::getBackupCount)
.orElseGet(() -> {
AgentConfig agentConfig = SpringUtil.getBean(AgentConfig.class);
AgentConfig.ProjectConfig project = agentConfig.getProject();
return project.getFileBackupCount();
});
.orElseGet(projectConfig::getFileBackupCount);
}
/**
@ -172,22 +188,23 @@ public class ProjectFileBackupUtil {
* @param projectInfoModel 项目
* @param backupId 要对比的备份ID
*/
public static void checkDiff(NodeProjectInfoModel projectInfoModel, String backupId) {
public void checkDiff(NodeProjectInfoModel projectInfoModel, String backupId) {
if (StrUtil.isEmpty(backupId)) {
// 备份ID 不存在
return;
}
String projectPath = projectInfoModel.allLib();
DslYmlDto dslYmlDto = projectInfoModel.dslConfig();
NodeProjectInfoModel infoModel = projectInfoService.resolveModel(projectInfoModel);
File projectPath = projectInfoService.resolveLibFile(infoModel);
DslYmlDto dslYmlDto = infoModel.dslConfig();
// 考虑到大文件对比比较耗时需要异步对比文件
ThreadUtil.execute(() -> {
try {
//String useBackupPath = resolveBackupPath(dslYmlDto);
File backupItemPath = ProjectFileBackupUtil.pathProjectBackup(projectInfoModel, backupId);
File backupPath = ProjectFileBackupUtil.pathProject(projectInfoModel);
File backupItemPath = this.pathProjectBackup(infoModel, backupId);
File backupPath = this.pathProject(infoModel);
// 获取文件列表
Map<String, File> backupFiles = ProjectFileBackupUtil.listFiles(backupItemPath.getAbsolutePath());
Map<String, File> nowFiles = ProjectFileBackupUtil.listFiles(projectPath);
Map<String, File> backupFiles = this.listFiles(backupItemPath);
Map<String, File> nowFiles = this.listFiles(projectPath);
nowFiles.forEach((fileSha1, file) -> {
// 当前目录存在的但是备份目录也存在的相同文件则删除
File backupFile = backupFiles.get(fileSha1);
@ -200,11 +217,7 @@ public class ProjectFileBackupUtil {
String[] backupSuffix = Optional.ofNullable(dslYmlDto)
.map(DslYmlDto::getFile)
.map(DslYmlDto.FileConfig::getBackupSuffix)
.orElseGet(() -> {
AgentConfig agentConfig = SpringUtil.getBean(AgentConfig.class);
AgentConfig.ProjectConfig project = agentConfig.getProject();
return project.getFileBackupSuffix();
});
.orElseGet(projectConfig::getFileBackupSuffix);
if (ArrayUtil.isNotEmpty(backupSuffix)) {
backupFiles.values()
.stream()
@ -225,7 +238,7 @@ public class ProjectFileBackupUtil {
// 检查备份保留个数
clearOldBackup(backupPath, dslYmlDto);
// 合并之前备份目录
margeBackupPath(projectInfoModel);
margeBackupPath(infoModel);
} catch (Exception e) {
log.warn("对比清空项目文件备份失败", e);
}
@ -237,9 +250,9 @@ public class ProjectFileBackupUtil {
*
* @param projectInfoModel 项目
*/
public static void margeBackupPath(NodeProjectInfoModel projectInfoModel) {
File backupPath = ProjectFileBackupUtil.pathProject(projectInfoModel);
File backupPathBefore = ProjectFileBackupUtil.pathProject(null, projectInfoModel.getId());
public void margeBackupPath(NodeProjectInfoModel projectInfoModel) {
File backupPath = this.pathProject(projectInfoModel);
File backupPathBefore = this.pathProject(null, projectInfoModel.getId());
if (FileUtil.isDirectory(backupPathBefore) && !FileUtil.equals(backupPathBefore, backupPath)) {
// 默认的备份路径存在并且现在的路径和默认的不一致
FileUtil.moveContent(backupPathBefore, backupPath, true);
@ -247,14 +260,14 @@ public class ProjectFileBackupUtil {
}
}
private static void loopClean(File backupPath) {
private void loopClean(File backupPath) {
if (FileUtil.isFile(backupPath)) {
return;
}
//
Optional.ofNullable(backupPath.listFiles()).ifPresent(files1 -> {
for (File file : files1) {
ProjectFileBackupUtil.loopClean(file);
this.loopClean(file);
}
});
// 检查目录是否为空
@ -269,7 +282,7 @@ public class ProjectFileBackupUtil {
* @param path 路径
* @return 文件列表信息
*/
private static Map<String, File> listFiles(String path) {
private Map<String, File> listFiles(File path) {
// 将所有的文件信息组装并签名
List<File> files = FileUtil.loopFiles(path);
List<JSONObject> collect = files.stream().map(file -> {

View File

@ -36,23 +36,27 @@ import java.util.List;
import java.util.stream.Collectors;
/**
* 白名单服务
* 授权服务
*
* @author bwcx_jzy
* @since 2019/2/28
*/
@Service
@Slf4j
public class WhitelistDirectoryService extends BaseDataService {
public class WhitelistDirectoryService extends BaseOperService<AgentWhitelist> {
public WhitelistDirectoryService() {
super(AgentConst.WHITELIST_DIRECTORY);
}
/**
* 获取白名单信息配置如何没有配置或者配置错误将返回新对象
* 获取授权信息配置如何没有配置或者配置错误将返回新对象
*
* @return AgentWhitelist
*/
public AgentWhitelist getWhitelist() {
try {
JSONObject jsonObject = getJSONObject(AgentConst.WHITELIST_DIRECTORY);
JSONObject jsonObject = getJSONObject();
if (jsonObject == null) {
return new AgentWhitelist();
}
@ -64,13 +68,13 @@ public class WhitelistDirectoryService extends BaseDataService {
}
/**
* 单项添加白名单
* 单项添加授权
*
* @param item 白名单
* @param item 授权
*/
public void addProjectWhiteList(String item) {
ArrayList<String> list = CollUtil.newArrayList(item);
List<String> checkOk = AgentWhitelist.covertToArray(list, "项目路径白名单不能位于Jpom目录下");
List<String> checkOk = AgentWhitelist.covertToArray(list, "项目路径授权不能位于Jpom目录下");
AgentWhitelist agentWhitelist = getWhitelist();
List<String> project = agentWhitelist.getProject();
@ -85,13 +89,13 @@ public class WhitelistDirectoryService extends BaseDataService {
public boolean checkProjectDirectory(String path) {
AgentWhitelist agentWhitelist = getWhitelist();
List<String> list = agentWhitelist.project();
List<String> list = agentWhitelist.getProject();
return AgentWhitelist.checkPath(list, path);
}
/**
* 保存白名单
* 保存授权
*
* @param jsonObject 实体
*/

View File

@ -1,89 +0,0 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2019 Code Technology Studio
*
* 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:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* 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.
*/
package org.dromara.jpom.service.manage;
import cn.hutool.core.date.DateUtil;
import org.dromara.jpom.common.commander.AbstractProjectCommander;
import org.dromara.jpom.common.commander.CommandOpResult;
import org.dromara.jpom.model.data.NodeProjectInfoModel;
import org.dromara.jpom.socket.ConsoleCommandOp;
import org.springframework.stereotype.Service;
/**
* 控制台
* Created by bwcx_jzy on 2018/9/28.
*
* @author bwcx_jzy
*/
@Service
public class ConsoleService {
private final ProjectInfoService projectInfoService;
public ConsoleService(ProjectInfoService projectInfoService) {
this.projectInfoService = projectInfoService;
}
/**
* 执行shell命令
*
* @param consoleCommandOp 执行的操作
* @param nodeProjectInfoModel 项目信息
* @return 执行结果
* @throws Exception 异常
*/
public CommandOpResult execCommand(ConsoleCommandOp consoleCommandOp, NodeProjectInfoModel nodeProjectInfoModel) throws Exception {
CommandOpResult result;
AbstractProjectCommander abstractProjectCommander = AbstractProjectCommander.getInstance();
// 执行命令
switch (consoleCommandOp) {
case restart:
result = abstractProjectCommander.restart(nodeProjectInfoModel);
break;
case start:
result = abstractProjectCommander.start(nodeProjectInfoModel);
break;
case stop:
result = abstractProjectCommander.stop(nodeProjectInfoModel);
break;
case status: {
result = abstractProjectCommander.status(nodeProjectInfoModel);
break;
}
case top:
case showlog:
default:
throw new IllegalArgumentException(consoleCommandOp + " error");
}
// 通知日志刷新
if (consoleCommandOp == ConsoleCommandOp.start || consoleCommandOp == ConsoleCommandOp.restart) {
// 修改 run lib 使用情况
NodeProjectInfoModel modify = projectInfoService.getItem(nodeProjectInfoModel.getId());
//
try {
projectInfoService.updateItem(modify);
} catch (Exception ignored) {
}
}
return result;
}
}

View File

@ -22,12 +22,15 @@
*/
package org.dromara.jpom.service.manage;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import org.dromara.jpom.common.AgentConst;
import org.dromara.jpom.model.RunMode;
import org.dromara.jpom.model.data.NodeProjectInfoModel;
import org.dromara.jpom.service.BaseWorkspaceOptService;
import org.dromara.jpom.system.ExtConfigBean;
import org.springframework.stereotype.Service;
import org.springframework.util.Assert;
import java.io.File;
@ -39,49 +42,142 @@ import java.io.File;
@Service
public class ProjectInfoService extends BaseWorkspaceOptService<NodeProjectInfoModel> {
public ProjectInfoService() {
super(AgentConst.PROJECT);
}
// public HashSet<String> getAllGroup() {
// //获取所有分组
// List<NodeProjectInfoModel> nodeProjectInfoModels = list();
// HashSet<String> hashSet = new HashSet<>();
// if (nodeProjectInfoModels == null) {
// return hashSet;
// }
// for (NodeProjectInfoModel nodeProjectInfoModel : nodeProjectInfoModels) {
// hashSet.add(nodeProjectInfoModel.getGroup());
// }
// return hashSet;
// }
@Override
public void addItem(NodeProjectInfoModel nodeProjectInfoModel) {
nodeProjectInfoModel.setCreateTime(DateUtil.now());
super.addItem(nodeProjectInfoModel);
public void updateItem(NodeProjectInfoModel data) {
super.updateItem(data);
}
/**
* 查看项目控制台日志文件大小
* 获取原始项目信息
*
* @param nodeProjectInfoModel 项目信息
* @return model
*/
public NodeProjectInfoModel resolveModel(NodeProjectInfoModel nodeProjectInfoModel) {
RunMode runMode = nodeProjectInfoModel.getRunMode();
if (runMode != RunMode.Link) {
return nodeProjectInfoModel;
}
NodeProjectInfoModel item = this.getItem(nodeProjectInfoModel.getLinkId());
Assert.notNull(item, "被软链的项目已经不存在啦," + nodeProjectInfoModel.getLinkId());
return item;
}
/**
* 解析lib路径
*
* @param nodeProjectInfoModel 项目
* @return 文件大小
* @return 项目的 lib 路径文件路径
*/
public String getLogSize(NodeProjectInfoModel nodeProjectInfoModel) {
if (nodeProjectInfoModel == null) {
return null;
public String resolveLibPath(NodeProjectInfoModel nodeProjectInfoModel) {
RunMode runMode = nodeProjectInfoModel.getRunMode();
if (runMode == RunMode.Link) {
NodeProjectInfoModel item = this.getItem(nodeProjectInfoModel.getLinkId());
Assert.notNull(item, "软链项目已经不存在啦");
return item.allLib();
}
File file = new File(nodeProjectInfoModel.getLog());
if (file.exists()) {
long fileSize = file.length();
if (fileSize <= 0) {
return null;
}
return FileUtil.readableFileSize(fileSize);
}
return null;
return nodeProjectInfoModel.allLib();
}
/**
* 解析lib路径
*
* @param nodeProjectInfoModel 项目
* @return 项目的 lib 路径文件路径
*/
public File resolveLibFile(NodeProjectInfoModel nodeProjectInfoModel) {
String path = this.resolveLibPath(nodeProjectInfoModel);
return FileUtil.file(path);
}
/**
* 解析项目的日志路径
*
* @param nodeProjectInfoModel 项目
* @return path
*/
private File resolveLogFile(NodeProjectInfoModel nodeProjectInfoModel, NodeProjectInfoModel originalModel) {
String id = nodeProjectInfoModel.getId();
File logPath = this.resolveLogPath(nodeProjectInfoModel, originalModel);
return FileUtil.file(logPath, id + ".log");
}
/**
* 解析项目的日志路径
*
* @param nodeProjectInfoModel 项目
* @return path
*/
private File resolveLogPath(NodeProjectInfoModel nodeProjectInfoModel, NodeProjectInfoModel originalModel) {
String id = nodeProjectInfoModel.getId();
Assert.hasText(id, "没有项目id");
String loggedPath = originalModel.logPath();
if (StrUtil.isNotEmpty(loggedPath)) {
return FileUtil.file(loggedPath, id);
}
String path = ExtConfigBean.getPath();
return FileUtil.file(path, "project-log", id);
}
/**
* 解析项目的日志路径
*
* @param nodeProjectInfoModel 项目
* @return path
*/
public String resolveAbsoluteLog(NodeProjectInfoModel nodeProjectInfoModel, NodeProjectInfoModel originalModel) {
File file = this.resolveAbsoluteLogFile(nodeProjectInfoModel, originalModel);
return FileUtil.getAbsolutePath(file);
}
/**
* 解析项目的日志路径
*
* @param nodeProjectInfoModel 项目
* @return path
*/
public File resolveAbsoluteLogFile(NodeProjectInfoModel nodeProjectInfoModel) {
NodeProjectInfoModel infoModel = this.resolveModel(nodeProjectInfoModel);
return this.resolveLogFile(nodeProjectInfoModel, infoModel);
}
/**
* 解析项目的日志路径
*
* @param nodeProjectInfoModel 项目
* @return path
*/
public File resolveAbsoluteLogFile(NodeProjectInfoModel nodeProjectInfoModel, NodeProjectInfoModel originalModel) {
File file = this.resolveLogFile(nodeProjectInfoModel, originalModel);
// auto create dir
FileUtil.touch(file);
return file;
}
/**
* 解析日志备份路径
*
* @param nodeProjectInfoModel 项目
* @return file
*/
public File resolveLogBack(NodeProjectInfoModel nodeProjectInfoModel) {
NodeProjectInfoModel infoModel = this.resolveModel(nodeProjectInfoModel);
return this.resolveLogBack(nodeProjectInfoModel, infoModel);
}
/**
* 解析日志备份路径
*
* @param nodeProjectInfoModel 项目
* @return file
*/
public File resolveLogBack(NodeProjectInfoModel nodeProjectInfoModel, NodeProjectInfoModel originalModel) {
File logPath = this.resolveLogPath(nodeProjectInfoModel, originalModel);
return FileUtil.file(logPath, "back");
}
}

View File

@ -0,0 +1,236 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2019 Code Technology Studio
*
* 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:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* 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.
*/
package org.dromara.jpom.service.script;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.Opt;
import cn.hutool.core.lang.Tuple;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.net.url.UrlQuery;
import cn.hutool.core.thread.ThreadUtil;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson2.JSONObject;
import org.dromara.jpom.JpomApplication;
import org.dromara.jpom.common.Const;
import org.dromara.jpom.exception.IllegalArgument2Exception;
import org.dromara.jpom.configuration.ProjectLogConfig;
import org.dromara.jpom.model.EnvironmentMapBuilder;
import org.dromara.jpom.model.data.DslYmlDto;
import org.dromara.jpom.model.data.NodeProjectInfoModel;
import org.dromara.jpom.model.data.NodeScriptModel;
import org.dromara.jpom.script.DslScriptBuilder;
import org.dromara.jpom.service.manage.ProjectInfoService;
import org.dromara.jpom.service.system.AgentWorkspaceEnvVarService;
import org.dromara.jpom.socket.ConsoleCommandOp;
import org.dromara.jpom.system.ExtConfigBean;
import org.dromara.jpom.util.CommandUtil;
import org.dromara.jpom.util.FileUtils;
import org.springframework.stereotype.Service;
import java.io.File;
import java.util.Map;
import java.util.concurrent.Future;
/**
* @author bwcx_jzy
* @since 23/12/30 030
*/
@Service
public class DslScriptServer {
private final AgentWorkspaceEnvVarService agentWorkspaceEnvVarService;
private final NodeScriptServer nodeScriptServer;
private final ProjectLogConfig logConfig;
private final JpomApplication jpomApplication;
private final ProjectInfoService projectInfoService;
public DslScriptServer(AgentWorkspaceEnvVarService agentWorkspaceEnvVarService,
NodeScriptServer nodeScriptServer,
ProjectLogConfig logConfig,
JpomApplication jpomApplication,
ProjectInfoService projectInfoService) {
this.agentWorkspaceEnvVarService = agentWorkspaceEnvVarService;
this.nodeScriptServer = nodeScriptServer;
this.logConfig = logConfig;
this.jpomApplication = jpomApplication;
this.projectInfoService = projectInfoService;
}
/**
* 异步执行
*
* @param scriptProcess 脚本流程
* @param log 日志
*/
public void run(DslYmlDto.BaseProcess scriptProcess, NodeProjectInfoModel nodeProjectInfoModel, NodeProjectInfoModel originalModel, String action, String log, boolean sync) throws Exception {
DslScriptBuilder builder = this.create(scriptProcess, nodeProjectInfoModel, originalModel, action, log);
Future<?> execute = ThreadUtil.execAsync(builder);
if (sync) {
execute.get();
}
}
/**
* 同步执行
*
* @param scriptProcess 脚本流程
*/
public Tuple syncRun(DslYmlDto.BaseProcess scriptProcess, NodeProjectInfoModel nodeProjectInfoModel, NodeProjectInfoModel originalModel, String action) {
try (DslScriptBuilder builder = this.create(scriptProcess, nodeProjectInfoModel, originalModel, action, null)) {
return builder.syncExecute();
}
}
/**
* 解析流程脚本信息
*
* @param nodeProjectInfoModel 项目信息
* @param dslYml dsl 配置信息
* @param op 流程
* @return data
*/
public Tuple resolveProcessScript(NodeProjectInfoModel nodeProjectInfoModel, DslYmlDto dslYml, ConsoleCommandOp op) {
DslYmlDto.BaseProcess baseProcess = NodeProjectInfoModel.tryDslProcess(dslYml, op.name());
return this.resolveProcessScript(nodeProjectInfoModel, baseProcess);
}
/**
* 解析流程脚本信息
*
* @param nodeProjectInfoModel 项目信息
* @param scriptProcess 流程
* @return data
*/
private Tuple resolveProcessScript(NodeProjectInfoModel nodeProjectInfoModel, DslYmlDto.BaseProcess scriptProcess) {
JSONObject jsonObject = new JSONObject();
jsonObject.put("status", false);
if (scriptProcess == null) {
jsonObject.put("msg", "流程不存在");
return new Tuple(jsonObject, null);
}
String scriptId = scriptProcess.getScriptId();
if (StrUtil.isEmpty(scriptId)) {
jsonObject.put("msg", "请填写脚本模板id");
return new Tuple(jsonObject, null);
}
//
NodeScriptModel item = nodeScriptServer.getItem(scriptId);
if (item != null) {
// 脚本存在
jsonObject.put("status", true);
jsonObject.put("type", "script");
jsonObject.put("scriptId", scriptId);
return new Tuple(jsonObject, item);
}
File lib = projectInfoService.resolveLibFile(nodeProjectInfoModel);
File scriptFile = FileUtil.file(lib, scriptId);
if (FileUtil.isFile(scriptFile)) {
// 文件存在
jsonObject.put("status", true);
jsonObject.put("type", "file");
jsonObject.put("scriptId", scriptId);
return new Tuple(jsonObject, scriptFile);
}
jsonObject.put("msg", "脚本模版不存在:" + scriptId);
return new Tuple(jsonObject, null);
}
/**
* 构建 DSL 执行器
*
* @param scriptProcess 脚本流程
* @param nodeProjectInfoModel 项目
* @param log 日志路径
* @param action 具体操作
*/
private DslScriptBuilder create(DslYmlDto.BaseProcess scriptProcess, NodeProjectInfoModel nodeProjectInfoModel, NodeProjectInfoModel originalModel, String action, String log) {
Tuple tuple = this.resolveProcessScript(originalModel, scriptProcess);
JSONObject jsonObject = tuple.get(0);
// 判断状态
boolean status = jsonObject.getBooleanValue("status");
cn.hutool.core.lang.Assert.isTrue(status, () -> {
String msg = jsonObject.getString("msg");
return new IllegalArgument2Exception(msg);
});
String type = jsonObject.getString("type");
EnvironmentMapBuilder environment = this.environment(nodeProjectInfoModel, scriptProcess);
environment.put("PROJECT_LOG_FILE", log);
File scriptFile;
boolean autoDelete = false;
if (StrUtil.equals(type, "file")) {
scriptFile = tuple.get(1);
} else {
NodeScriptModel item = tuple.get(1);
scriptFile = this.initScriptFile(item);
// 系统生成的脚本需要自动删除
autoDelete = true;
}
DslScriptBuilder builder = new DslScriptBuilder(action, environment, scriptProcess.getScriptArgs(), log, logConfig.getFileCharset());
builder.setScriptFile(scriptFile);
builder.setAutoDelete(autoDelete);
return builder;
}
/**
* 创建脚本文件
*
* @param scriptModel 脚本对象
* @return file
*/
private File initScriptFile(NodeScriptModel scriptModel) {
String dataPath = jpomApplication.getDataPath();
File scriptFile = FileUtil.file(dataPath, Const.SCRIPT_RUN_CACHE_DIRECTORY, StrUtil.format("{}.{}", IdUtil.fastSimpleUUID(), CommandUtil.SUFFIX));
// 替换内容
String context = scriptModel.getContext();
FileUtils.writeScript(context, scriptFile, ExtConfigBean.getConsoleLogCharset());
return scriptFile;
}
private EnvironmentMapBuilder environment(NodeProjectInfoModel nodeProjectInfoModel, DslYmlDto.BaseProcess scriptProcess) {
//
EnvironmentMapBuilder environmentMapBuilder = agentWorkspaceEnvVarService.getEnv(nodeProjectInfoModel.getWorkspaceId());
// 项目配置的环境变量
String dslEnv = nodeProjectInfoModel.getDslEnv();
Opt.ofBlankAble(dslEnv)
.map(s -> UrlQuery.of(s, CharsetUtil.CHARSET_UTF_8))
.map(UrlQuery::getQueryMap)
.map(map -> {
Map<String, String> map1 = MapUtil.newHashMap();
for (Map.Entry<CharSequence, CharSequence> entry : map.entrySet()) {
map1.put(StrUtil.toString(entry.getKey()), StrUtil.toString(entry.getValue()));
}
return map1;
})
.ifPresent(environmentMapBuilder::putStr);
String lib = projectInfoService.resolveLibPath(nodeProjectInfoModel);
//
environmentMapBuilder
.putStr(scriptProcess.getScriptEnv())
.put("PROJECT_ID", nodeProjectInfoModel.getId())
.put("PROJECT_NAME", nodeProjectInfoModel.getName())
.put("PROJECT_PATH", lib);
return environmentMapBuilder;
}
}

View File

@ -35,6 +35,7 @@ import com.alibaba.fastjson2.TypeReference;
import lombok.extern.slf4j.Slf4j;
import org.dromara.jpom.JpomApplication;
import org.dromara.jpom.common.Const;
import org.dromara.jpom.configuration.AgentConfig;
import org.dromara.jpom.model.EnvironmentMapBuilder;
import org.dromara.jpom.system.ExtConfigBean;
import org.dromara.jpom.util.CommandUtil;
@ -67,8 +68,8 @@ public class AgentFreeWebSocketScriptHandle extends BaseAgentWebSocketHandle {
private final static Map<String, ScriptProcess> CACHE = new SafeConcurrentHashMap<>();
@Autowired
public void init() {
public void init(AgentConfig agentConfig) {
setAgentAuthorize(agentConfig.getAuthorize());
}
@OnOpen
@ -124,8 +125,8 @@ public class AgentFreeWebSocketScriptHandle extends BaseAgentWebSocketHandle {
@Override
@OnClose
public void onClose(Session session) {
super.onClose(session);
public void onClose(Session session, CloseReason closeReason) {
super.onClose(session, closeReason);
IoUtil.close(CACHE.remove(session.getId()));
}
@ -195,7 +196,7 @@ public class AgentFreeWebSocketScriptHandle extends BaseAgentWebSocketHandle {
@Override
public void close() throws Exception {
IoUtil.close(inputStream);
Optional.ofNullable(process).ifPresent(Process::destroy);
CommandUtil.kill(process);
try {
FileUtil.del(this.scriptFile);
} catch (Exception ignored) {

View File

@ -32,10 +32,13 @@ import com.alibaba.fastjson2.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.dromara.jpom.common.Const;
import org.dromara.jpom.common.commander.CommandOpResult;
import org.dromara.jpom.common.commander.ProjectCommander;
import org.dromara.jpom.configuration.ProjectLogConfig;
import org.dromara.jpom.model.RunMode;
import org.dromara.jpom.model.data.DslYmlDto;
import org.dromara.jpom.model.data.NodeProjectInfoModel;
import org.dromara.jpom.service.manage.ConsoleService;
import org.dromara.jpom.service.manage.ProjectInfoService;
import org.dromara.jpom.system.AgentConfig;
import org.dromara.jpom.configuration.AgentConfig;
import org.dromara.jpom.util.FileSearchUtil;
import org.dromara.jpom.util.SocketSessionUtil;
import org.springframework.beans.factory.annotation.Autowired;
@ -59,16 +62,17 @@ import java.nio.charset.Charset;
public class AgentWebSocketConsoleHandle extends BaseAgentWebSocketHandle {
private static ProjectInfoService projectInfoService;
private static ConsoleService consoleService;
private static AgentConfig.ProjectConfig.LogConfig logConfig;
private static ProjectLogConfig logConfig;
private static ProjectCommander projectCommander;
@Autowired
public void init(ProjectInfoService projectInfoService,
ConsoleService consoleService,
AgentConfig agentConfig) {
AgentConfig agentConfig,
ProjectCommander projectCommander) {
AgentWebSocketConsoleHandle.projectInfoService = projectInfoService;
AgentWebSocketConsoleHandle.consoleService = consoleService;
AgentWebSocketConsoleHandle.logConfig = agentConfig.getProject().getLog();
AgentWebSocketConsoleHandle.projectCommander = projectCommander;
setAgentAuthorize(agentConfig.getAuthorize());
}
@OnOpen
@ -139,6 +143,14 @@ public class AgentWebSocketConsoleHandle extends BaseAgentWebSocketHandle {
if (nodeProjectInfoModel == null) {
return;
}
// DSL
RunMode runMode = nodeProjectInfoModel.getRunMode();
if (runMode == RunMode.Dsl) {
// 判断是否可以执行 reload 事件
DslYmlDto dslYmlDto = nodeProjectInfoModel.mustDslConfig();
boolean b = dslYmlDto.hasRunProcess(ConsoleCommandOp.reload.name());
json.put("canReload", b);
}
runMsg(consoleCommandOp, session, nodeProjectInfoModel, json);
}
@ -153,28 +165,20 @@ public class AgentWebSocketConsoleHandle extends BaseAgentWebSocketHandle {
switch (consoleCommandOp) {
case start:
case restart:
case stop:
case reload:
logUser = true;
strResult = consoleService.execCommand(consoleCommandOp, nodeProjectInfoModel);
strResult = projectCommander.execCommand(consoleCommandOp, nodeProjectInfoModel);
if (strResult.isSuccess()) {
resultData = new JsonMessage<>(200, "操作成功", strResult);
} else {
resultData = new JsonMessage<>(400, strResult.msgStr());
}
break;
case stop: {
logUser = true;
// 停止项目
strResult = consoleService.execCommand(consoleCommandOp, nodeProjectInfoModel);
if (strResult.isSuccess()) {
resultData = new JsonMessage<>(200, "操作成功", strResult);
} else {
resultData = new JsonMessage<>(400, strResult.msgStr());
}
break;
}
case status: {
// 获取项目状态
strResult = consoleService.execCommand(consoleCommandOp, nodeProjectInfoModel);
strResult = projectCommander.execCommand(consoleCommandOp, nodeProjectInfoModel);
if (strResult.isSuccess()) {
resultData = new JsonMessage<>(200, "运行中", strResult);
} else {
@ -204,17 +208,16 @@ public class AgentWebSocketConsoleHandle extends BaseAgentWebSocketHandle {
} finally {
if (logUser) {
// 记录操作人
NodeProjectInfoModel newNodeProjectInfoModel = projectInfoService.getItem(nodeProjectInfoModel.getId());
NodeProjectInfoModel update = new NodeProjectInfoModel();
String name = getOptUserName(session);
newNodeProjectInfoModel.setModifyUser(name);
projectInfoService.updateItem(newNodeProjectInfoModel);
update.setModifyUser(name);
projectInfoService.updateById(update, nodeProjectInfoModel.getId());
}
}
// 返回数据
if (resultData != null) {
reqJson.putAll(resultData.toJson());
reqJson.put(Const.SOCKET_MSG_TAG, Const.SOCKET_MSG_TAG);
log.info(reqJson.toString());
SocketSessionUtil.send(session, reqJson.toString());
}
}
@ -242,7 +245,8 @@ public class AgentWebSocketConsoleHandle extends BaseAgentWebSocketHandle {
private JsonMessage<Object> searchLog(Session session, NodeProjectInfoModel nodeProjectInfoModel, JSONObject reqJson) {
//
String fileName = reqJson.getString("logFile");
File file = FileUtil.file(nodeProjectInfoModel.allLib(), fileName);
File libFile = projectInfoService.resolveLibFile(nodeProjectInfoModel);
File file = FileUtil.file(libFile, fileName);
if (!FileUtil.isFile(file)) {
return new JsonMessage<>(404, "文件不存在");
}
@ -281,9 +285,10 @@ public class AgentWebSocketConsoleHandle extends BaseAgentWebSocketHandle {
String fileName = reqJson.getString("fileName");
File file;
if (StrUtil.isEmpty(fileName)) {
file = new File(nodeProjectInfoModel.getLog());
file = projectInfoService.resolveAbsoluteLogFile(nodeProjectInfoModel);
} else {
file = FileUtil.file(nodeProjectInfoModel.allLib(), fileName);
File libFile = projectInfoService.resolveLibFile(nodeProjectInfoModel);
file = FileUtil.file(libFile, fileName);
}
try {
Charset charset = logConfig.getFileCharset();
@ -299,8 +304,8 @@ public class AgentWebSocketConsoleHandle extends BaseAgentWebSocketHandle {
@Override
@OnClose
public void onClose(Session session) {
super.onClose(session);
public void onClose(Session session, CloseReason closeReason) {
super.onClose(session, closeReason);
AgentFileTailWatcher.offline(session);
}

View File

@ -26,6 +26,7 @@ import cn.hutool.core.util.StrUtil;
import cn.keepbx.jpom.model.JsonMessage;
import com.alibaba.fastjson2.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.dromara.jpom.configuration.AgentConfig;
import org.dromara.jpom.model.data.NodeScriptModel;
import org.dromara.jpom.script.NodeScriptProcessBuilder;
import org.dromara.jpom.service.script.NodeScriptServer;
@ -51,8 +52,9 @@ public class AgentWebSocketScriptHandle extends BaseAgentWebSocketHandle {
private static NodeScriptServer nodeScriptServer;
@Autowired
public void init(NodeScriptServer nodeScriptServer) {
public void init(NodeScriptServer nodeScriptServer, AgentConfig agentConfig) {
AgentWebSocketScriptHandle.nodeScriptServer = nodeScriptServer;
setAgentAuthorize(agentConfig.getAuthorize());
}
@OnOpen
@ -137,8 +139,8 @@ public class AgentWebSocketScriptHandle extends BaseAgentWebSocketHandle {
@Override
@OnClose
public void onClose(Session session) {
super.onClose(session);
public void onClose(Session session, CloseReason closeReason) {
super.onClose(session, closeReason);
NodeScriptProcessBuilder.stopWatcher(session);
}

View File

@ -28,7 +28,8 @@ import cn.hutool.core.map.SafeConcurrentHashMap;
import cn.keepbx.jpom.model.JsonMessage;
import com.alibaba.fastjson2.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.dromara.jpom.system.AgentConfig;
import org.dromara.jpom.configuration.AgentConfig;
import org.dromara.jpom.configuration.SystemConfig;
import org.dromara.jpom.system.LogbackConfig;
import org.dromara.jpom.util.SocketSessionUtil;
import org.springframework.beans.factory.annotation.Autowired;
@ -51,13 +52,14 @@ import java.util.Map;
@Slf4j
public class AgentWebSocketSystemLogHandle extends BaseAgentWebSocketHandle {
private static AgentConfig.SystemConfig systemConfig;
private static SystemConfig systemConfig;
private static final Map<String, File> CACHE_FILE = new SafeConcurrentHashMap<>();
@Autowired
public void init(AgentConfig agentConfig) {
AgentWebSocketSystemLogHandle.systemConfig = agentConfig.getSystem();
setAgentAuthorize(agentConfig.getAuthorize());
}
@OnOpen
@ -115,8 +117,8 @@ public class AgentWebSocketSystemLogHandle extends BaseAgentWebSocketHandle {
@Override
@OnClose
public void onClose(Session session) {
super.onClose(session);
public void onClose(Session session, CloseReason closeReason) {
super.onClose(session, closeReason);
}
@OnError

View File

@ -33,7 +33,7 @@ import org.dromara.jpom.common.JpomManifest;
import org.dromara.jpom.model.AgentFileModel;
import org.dromara.jpom.model.UploadFileModel;
import org.dromara.jpom.model.WebSocketMessageModel;
import org.dromara.jpom.system.AgentConfig;
import org.dromara.jpom.configuration.AgentConfig;
import org.dromara.jpom.util.SocketSessionUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.web.servlet.MultipartProperties;
@ -66,6 +66,7 @@ public class AgentWebSocketUpdateHandle extends BaseAgentWebSocketHandle {
public void init(AgentConfig agentConfig, MultipartProperties multipartProperties) {
AgentWebSocketUpdateHandle.agentConfig = agentConfig;
AgentWebSocketUpdateHandle.multipartProperties = multipartProperties;
setAgentAuthorize(agentConfig.getAuthorize());
}
@OnOpen
@ -157,8 +158,8 @@ public class AgentWebSocketUpdateHandle extends BaseAgentWebSocketHandle {
@Override
@OnClose
public void onClose(Session session) {
super.onClose(session);
public void onClose(Session session, CloseReason closeReason) {
super.onClose(session, closeReason);
UPLOAD_FILE_INFO.remove(session.getId());
}

View File

@ -27,11 +27,10 @@ import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.map.SafeConcurrentHashMap;
import cn.hutool.core.util.StrUtil;
import cn.hutool.core.util.URLUtil;
import cn.hutool.extra.spring.SpringUtil;
import com.alibaba.fastjson2.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.dromara.jpom.common.Const;
import org.dromara.jpom.system.AgentAuthorize;
import org.dromara.jpom.configuration.AgentAuthorize;
import org.dromara.jpom.util.SocketSessionUtil;
import javax.websocket.CloseReason;
@ -41,8 +40,6 @@ import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import static javax.websocket.CloseReason.CloseCodes.CANNOT_ACCEPT;
/**
* 插件端socket 基类
*
@ -53,6 +50,16 @@ import static javax.websocket.CloseReason.CloseCodes.CANNOT_ACCEPT;
public abstract class BaseAgentWebSocketHandle {
private static final ConcurrentHashMap<String, String> USER = new SafeConcurrentHashMap<>();
protected static AgentAuthorize agentAuthorize;
/**
* 设置授权对象
*
* @param agentAuthorize 授权
*/
protected static void setAgentAuthorize(AgentAuthorize agentAuthorize) {
BaseAgentWebSocketHandle.agentAuthorize = agentAuthorize;
}
protected String getParameters(Session session, String name) {
Map<String, List<String>> requestParameterMap = session.getRequestParameterMap();
@ -76,12 +83,11 @@ public abstract class BaseAgentWebSocketHandle {
*/
public boolean checkAuthorize(Session session) {
String authorize = this.getParameters(session, Const.JPOM_AGENT_AUTHORIZE);
AgentAuthorize agentAuthorize = SpringUtil.getBean(AgentAuthorize.class);
boolean ok = agentAuthorize.checkAuthorize(authorize);
if (!ok) {
log.warn("socket 会话建立失败,授权信息错误");
try {
session.close(new CloseReason(CANNOT_ACCEPT, "授权信息错误"));
session.close(new CloseReason(CloseReason.CloseCodes.CANNOT_ACCEPT, "授权信息错误"));
} catch (Exception e) {
log.error("socket 错误", e);
}
@ -119,7 +125,8 @@ public abstract class BaseAgentWebSocketHandle {
return StrUtil.emptyToDefault(name, StrUtil.DASHED);
}
public void onClose(Session session) {
public void onClose(Session session, CloseReason closeReason) {
log.debug("会话[{}]关闭原因:{}", session.getId(), closeReason);
// 清理日志监听
try {
AgentFileTailWatcher.offline(session);

View File

@ -41,11 +41,16 @@ import lombok.extern.slf4j.Slf4j;
import org.dromara.jpom.JpomApplication;
import org.dromara.jpom.common.ILoadEvent;
import org.dromara.jpom.common.RemoteVersion;
import org.dromara.jpom.common.commander.AbstractProjectCommander;
import org.dromara.jpom.common.commander.ProjectCommander;
import org.dromara.jpom.configuration.AgentAuthorize;
import org.dromara.jpom.configuration.AgentConfig;
import org.dromara.jpom.configuration.ProjectLogConfig;
import org.dromara.jpom.cron.CronUtils;
import org.dromara.jpom.model.RunMode;
import org.dromara.jpom.model.data.NodeProjectInfoModel;
import org.dromara.jpom.script.BaseRunScript;
import org.dromara.jpom.service.manage.ProjectInfoService;
import org.dromara.jpom.socket.ConsoleCommandOp;
import org.dromara.jpom.util.CommandUtil;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Configuration;
@ -75,23 +80,26 @@ public class AgentStartInit implements ILoadEvent, ISystemTask {
private final AgentConfig agentConfig;
private final AgentAuthorize agentAuthorize;
private final JpomApplication jpomApplication;
private final ProjectCommander projectCommander;
private final ProjectLogConfig projectLogConfig;
public AgentStartInit(ProjectInfoService projectInfoService,
AgentConfig agentConfig,
AgentAuthorize agentAuthorize,
JpomApplication jpomApplication) {
JpomApplication jpomApplication,
ProjectCommander projectCommander) {
this.projectInfoService = projectInfoService;
this.agentConfig = agentConfig;
this.agentAuthorize = agentAuthorize;
this.agentAuthorize = agentConfig.getAuthorize();
this.jpomApplication = jpomApplication;
this.projectCommander = projectCommander;
projectLogConfig = agentConfig.getProject().getLog();
}
private void startAutoBackLog() {
AgentConfig.ProjectConfig.LogConfig logConfig = agentConfig.getProject().getLog();
// 获取cron 表达式
String cron = Opt.ofBlankAble(logConfig.getAutoBackupConsoleCron()).orElse("0 0/10 * * * ?");
String cron = Opt.ofBlankAble(projectLogConfig.getAutoBackupConsoleCron()).orElse("0 0/10 * * * ?");
//
CronUtils.upsert(ID, cron, () -> {
try {
@ -108,28 +116,27 @@ public class AgentStartInit implements ILoadEvent, ISystemTask {
}
private void checkProject(NodeProjectInfoModel nodeProjectInfoModel) {
File file = new File(nodeProjectInfoModel.getLog());
File file = projectInfoService.resolveAbsoluteLogFile(nodeProjectInfoModel);
if (!file.exists()) {
return;
}
AgentConfig.ProjectConfig.LogConfig logConfig = agentConfig.getProject().getLog();
DataSize autoBackSize = logConfig.getAutoBackupSize();
DataSize autoBackSize = projectLogConfig.getAutoBackupSize();
autoBackSize = Optional.ofNullable(autoBackSize).orElseGet(() -> DataSize.ofMegabytes(50));
long len = file.length();
if (len > autoBackSize.toBytes()) {
try {
AbstractProjectCommander.getInstance().backLog(nodeProjectInfoModel);
projectCommander.backLog(nodeProjectInfoModel);
} catch (Exception e) {
log.warn("auto back log", e);
}
}
// 清理过期的文件
File logFile = nodeProjectInfoModel.getLogBack();
File logFile = projectInfoService.resolveLogBack(nodeProjectInfoModel);
DateTime nowTime = DateTime.now();
List<File> files = FileUtil.loopFiles(logFile, pathname -> {
DateTime dateTime = DateUtil.date(pathname.lastModified());
long days = DateUtil.betweenDay(dateTime, nowTime, false);
long saveDays = logConfig.getSaveDays();
long saveDays = projectLogConfig.getSaveDays();
return days > saveDays;
});
files.forEach(FileUtil::del);
@ -162,25 +169,54 @@ public class AgentStartInit implements ILoadEvent, ISystemTask {
}
}
/**
* 尝试开启项目
*/
private void autoStartProject() {
List<NodeProjectInfoModel> list = projectInfoService.list();
if (CollUtil.isEmpty(list)) {
List<NodeProjectInfoModel> allProject = projectInfoService.list();
if (CollUtil.isEmpty(allProject)) {
return;
}
list = list.stream().filter(nodeProjectInfoModel -> nodeProjectInfoModel.getAutoStart() != null && nodeProjectInfoModel.getAutoStart()).collect(Collectors.toList());
List<NodeProjectInfoModel> finalList = list;
List<NodeProjectInfoModel> startList = allProject.stream()
.filter(nodeProjectInfoModel -> nodeProjectInfoModel.getAutoStart() != null && nodeProjectInfoModel.getAutoStart())
.collect(Collectors.toList());
ThreadUtil.execute(() -> {
AbstractProjectCommander instance = AbstractProjectCommander.getInstance();
for (NodeProjectInfoModel nodeProjectInfoModel : finalList) {
for (NodeProjectInfoModel nodeProjectInfoModel : startList) {
try {
if (!instance.isRun(nodeProjectInfoModel)) {
instance.start(nodeProjectInfoModel);
if (!projectCommander.isRun(nodeProjectInfoModel)) {
projectCommander.execCommand(ConsoleCommandOp.start, nodeProjectInfoModel);
}
} catch (Exception e) {
log.warn("自动启动项目失败:{} {}", nodeProjectInfoModel.getId(), e.getMessage());
}
}
});
// 迁移备份日志文件
allProject.stream()
.filter(nodeProjectInfoModel -> nodeProjectInfoModel.getRunMode() != RunMode.Link)
.filter(nodeProjectInfoModel -> StrUtil.isEmpty(nodeProjectInfoModel.getLogPath()))
.forEach(nodeProjectInfoModel -> {
String logPath = new File(nodeProjectInfoModel.allLib()).getParent();
String log1 = FileUtil.normalize(String.format("%s/%s.log", logPath, nodeProjectInfoModel.getId()));
File logBack = new File(log1 + "_back");
if (FileUtil.isDirectory(logBack)) {
File resolveLogBack = projectInfoService.resolveLogBack(nodeProjectInfoModel);
FileUtil.mkdir(resolveLogBack);
log.info("自动迁移存在备份日志 {} -> {}", logBack.getAbsolutePath(), resolveLogBack);
FileUtil.moveContent(logBack, resolveLogBack, true);
FileUtil.del(logBack);
}
if (FileUtil.isFile(log1)) {
if (projectCommander.isRun(nodeProjectInfoModel)) {
log.warn("存在旧版项目日志但项目在运行中需要停止运行后手动迁移:{} {}", nodeProjectInfoModel.getName(), log1);
} else {
File resolveLogBack = projectInfoService.resolveLogBack(nodeProjectInfoModel);
FileUtil.mkdir(resolveLogBack);
log.info("自动迁移存在日志 {} -> {}", log1, resolveLogBack);
FileUtil.move(FileUtil.file(log1), resolveLogBack, true);
}
}
});
}

View File

@ -6,9 +6,6 @@ jpom:
agent-name: jpomAgent
# agent 端管理密码非必填如果为空Jpom 会自动生成一串随机字符串当密码
agent-pwd:
whitelist:
# 白名单目录是否验证包含关系
check-starts-with: true
project:
# 停止、启动项目(项目状态检测)等待的时长 单位秒
status-wait-time: 10

View File

@ -6,9 +6,6 @@ jpom:
agent-name: jpomAgent
# agent 端管理密码非必填如果为空Jpom 会自动生成一串随机字符串当密码
agent-pwd:
whitelist:
# 白名单目录是否验证包含关系
check-starts-with: true
project:
# 停止、启动项目(项目状态检测)等待的时长 单位秒
status-wait-time: 10

View File

@ -29,13 +29,13 @@
<parent>
<artifactId>jpom-parent</artifactId>
<groupId>org.dromara.jpom</groupId>
<version>2.10.47</version>
<version>2.11.0.8</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<name>Jpom Common</name>
<artifactId>common</artifactId>
<version>2.10.47</version>
<version>2.11.0.8</version>
<dependencies>
@ -79,7 +79,6 @@
<artifactId>hutool-cron</artifactId>
</dependency>
<!-- aop-->
<dependency>
<groupId>org.springframework.boot</groupId>

View File

@ -216,13 +216,14 @@ public class JpomApplication implements DisposableBean, InitializingBean {
if (SystemUtil.getOsInfo().isWindows()) {
//String result = CommandUtil.execSystemCommand(command, scriptFile.getParentFile());
//log.debug("windows restart {}", result);
CommandUtil.asyncExeLocalCommand(parentFile, "start /b" + command);
CommandUtil.asyncExeLocalCommand("start /b" + command, parentFile);
} else {
String jpomService = SystemUtil.get("JPOM_SERVICE");
if (StrUtil.isEmpty(jpomService)) {
CommandUtil.asyncExeLocalCommand(parentFile, command);
CommandUtil.asyncExeLocalCommand(command, parentFile);
} else {
CommandUtil.asyncExeLocalCommand(parentFile, "systemctl restart " + jpomService);
// 使用了服务
CommandUtil.asyncExeLocalCommand("systemctl restart " + jpomService, parentFile);
}
}
} catch (Exception e) {

View File

@ -30,7 +30,6 @@ import cn.hutool.extra.servlet.ServletUtil;
import lombok.Lombok;
import lombok.extern.slf4j.Slf4j;
import org.apache.tomcat.util.http.fileupload.servlet.ServletFileUpload;
import org.dromara.jpom.common.multipart.MultipartFileBuilder;
import org.dromara.jpom.util.FileUtils;
import org.springframework.util.Assert;
import org.springframework.web.context.request.RequestAttributes;
@ -47,7 +46,6 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
import java.util.Arrays;
import java.util.Map;
import java.util.Objects;
/**
@ -228,24 +226,6 @@ public abstract class BaseJpomController {
throw new IllegalArgumentException("not MultipartHttpServletRequest");
}
/**
* 判断是否存在文件
*
* @return true 存在文件
*/
protected boolean hasFile() {
Map<String, MultipartFile> fileMap = getMultiRequest().getFileMap();
return fileMap.size() > 0;
}
/**
* 创建多文件上传对象
*
* @return MultipartFileBuilder
*/
protected MultipartFileBuilder createMultipart() {
return new MultipartFileBuilder(getMultiRequest());
}
// ------------------------文件上传结束
/**
@ -346,7 +326,7 @@ public abstract class BaseJpomController {
protected void setApplicationHeader(HttpServletResponse response, String fileName) {
String contentType = ObjectUtil.defaultIfNull(FileUtil.getMimeType(fileName), "application/octet-stream");
response.setHeader("Content-Disposition", StrUtil.format("attachment;filename=\"{}\"",
URLUtil.encode(fileName, CharsetUtil.CHARSET_UTF_8)));
URLUtil.encode(fileName, CharsetUtil.CHARSET_UTF_8)));
response.setContentType(contentType);
}

View File

@ -141,8 +141,15 @@ public class JpomManifest {
// Windows NT 10.0; Win64; x64
OsInfo osInfo = SystemUtil.getOsInfo();
JavaInfo javaInfo = SystemUtil.getJavaInfo();
boolean inDocker = StrUtil.isNotEmpty(SystemUtil.get("JPOM_PKG"));
String osName = Opt.ofBlankAble(osInfo.getName()).orElseGet(() -> {
if (inDocker) {
return "docker";
}
return UserAgentInfo.NameUnknown;
});
return StrUtil.format("{} {}; {}; {}",
Opt.ofBlankAble(osInfo.getName()).orElse(UserAgentInfo.NameUnknown),
osName,
Opt.ofBlankAble(osInfo.getVersion()).orElse("0"),
Opt.ofBlankAble(osInfo.getArch()).orElse(UserAgentInfo.NameUnknown),
Opt.ofBlankAble(javaInfo.getVersion()).orElse(UserAgentInfo.NameUnknown)

View File

@ -61,6 +61,10 @@ public class ServerOpenApi {
* 文件下载
*/
public static final String FILE_STORAGE_DOWNLOAD = API + "file-storage/download/{id}/{token}";
/**
* 静态文件下载
*/
public static final String STATIC_FILE_STORAGE_DOWNLOAD = API + "file-storage/static/download/{id}/{token}";
/**
* 获取当前构建状态
*/

View File

@ -1,371 +0,0 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2019 Code Technology Studio
*
* 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:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* 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.
*/
package org.dromara.jpom.common.multipart;
import ch.qos.logback.core.util.FileSize;
import cn.hutool.core.io.FileTypeUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.text.UnicodeUtil;
import cn.hutool.core.util.ArrayUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import org.springframework.util.Assert;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.multipart.MultipartHttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
/**
* 文件上传builder
*
* @author bwcx_jzy
* @since 2018/10/23
*/
public class MultipartFileBuilder {
private final MultipartHttpServletRequest multipartHttpServletRequest;
/**
* 限制上传文件的大小
*/
private long maxSize = 0;
/**
* 字段名称
*/
private final Set<String> fieldNames = new HashSet<>();
/**
* 多文件上传
*/
private boolean multiple;
/**
* 文件名后缀
*/
private String[] fileExt;
/**
* 文件类型
*
* @see FileUtil#getMimeType(String)
*/
private String contentTypePrefix;
/**
* 文件流类型
*
* @see FileUtil#getType(File)
*/
private String[] inputStreamType;
/**
* 保存路径
*/
private String savePath;
/**
* 使用原文件名
*/
private boolean useOriginalFilename;
/**
* 文件上传大小限制
*
* @param maxSize 字节大小
* @return this
*/
public MultipartFileBuilder setMaxSize(long maxSize) {
this.maxSize = maxSize;
return this;
}
/**
* 文件上传大小限制
*
* @param maxSize 字符串
* @return this
*/
public MultipartFileBuilder setMaxSize(String maxSize) {
this.maxSize = FileSize.valueOf(maxSize).getSize();
return this;
}
/**
* 是否使用原文件名保存
*
* @param useOriginalFilename true
* @return this
*/
public MultipartFileBuilder setUseOriginalFilename(boolean useOriginalFilename) {
this.useOriginalFilename = useOriginalFilename;
return this;
}
/**
* 需要接受的文件字段
*
* @param fieldName 参数名
* @return this
*/
public MultipartFileBuilder addFieldName(String fieldName) {
this.fieldNames.add(fieldName);
return this;
}
/**
* 清空数据并重新赋值
*
* @param fieldName 参数名
* @return this
*/
public MultipartFileBuilder resetFieldName(String fieldName) {
this.fieldNames.clear();
this.fieldNames.add(fieldName);
return this;
}
/**
* 是否为多文件上传
*
* @param multiple true
* @return this
*/
public MultipartFileBuilder setMultiple(boolean multiple) {
this.multiple = multiple;
return this;
}
/**
* 限制文件后缀名
*
* @param fileExt 后缀
* @return this
*/
public MultipartFileBuilder setFileExt(String... fileExt) {
this.fileExt = fileExt;
return this;
}
/**
* 限制文件流类型
*
* @param inputStreamType type
* @return this
* @see FileTypeUtil#getType(InputStream)
*/
public MultipartFileBuilder setInputStreamType(String... inputStreamType) {
this.inputStreamType = inputStreamType;
return this;
}
/**
* 使用 获取到类型
*
* @param contentTypePrefix 前缀
* @return this
* @see FileUtil#getMimeType(String)
*/
public MultipartFileBuilder setContentTypePrefix(String contentTypePrefix) {
this.contentTypePrefix = contentTypePrefix;
return this;
}
/**
* 文件保存的路径
*
* @param savePath 路径
* @return this
*/
public MultipartFileBuilder setSavePath(String savePath) {
this.savePath = savePath;
return this;
}
private void checkSaveOne() {
if (this.fieldNames.size() != 1) {
throw new IllegalArgumentException("fieldNames size:" + this.fieldNames.size() + " use saves");
}
if (this.multiple) {
throw new IllegalArgumentException("multiple use saves");
}
}
/**
* 接收单文件上传
*
* @return 本地路径
* @throws IOException IO
*/
public String save() throws IOException {
checkSaveOne();
String[] paths = saves();
return paths[0];
}
/**
* 保存多个文件
*
* @return 本地路径数组
* @throws IOException IO
*/
public String[] saves() throws IOException {
if (fieldNames.isEmpty()) {
throw new IllegalArgumentException("fieldNames:empty");
}
String[] paths = new String[fieldNames.size()];
int index = 0;
for (String fieldName : fieldNames) {
if (this.multiple) {
List<MultipartFile> multipartFiles = multipartHttpServletRequest.getFiles(fieldName);
for (MultipartFile multipartFile : multipartFiles) {
paths[index++] = saveAndName(multipartFile)[0];
}
} else {
MultipartFile multipartFile = multipartHttpServletRequest.getFile(fieldName);
paths[index++] = saveAndName(multipartFile)[0];
}
}
return paths;
}
/**
* 上传文件并且返回原文件名
*
* @return 数组
* @throws IOException IO
*/
public String[] saveAndName() throws IOException {
checkSaveOne();
List<String[]> list = saveAndNames();
return list.get(0);
}
/**
* 上传文件并且返回原文件名
*
* @return 集合
* @throws IOException IO
*/
public List<String[]> saveAndNames() throws IOException {
if (fieldNames.isEmpty()) {
throw new IllegalArgumentException("fieldNames:empty");
}
List<String[]> list = new ArrayList<>();
for (String fieldName : fieldNames) {
if (this.multiple) {
List<MultipartFile> multipartFiles = multipartHttpServletRequest.getFiles(fieldName);
for (MultipartFile multipartFile : multipartFiles) {
String[] info = saveAndName(multipartFile);
list.add(info);
}
} else {
MultipartFile multipartFile = multipartHttpServletRequest.getFile(fieldName);
String[] info = saveAndName(multipartFile);
list.add(info);
}
}
return list;
}
/**
* 保存文件并验证类型
*
* @param multiFile file
* @return 本地路径和原文件名
* @throws IOException IO
*/
private String[] saveAndName(MultipartFile multiFile) throws IOException {
Assert.notNull(multiFile, "not null");
String fileName = multiFile.getOriginalFilename();
if (StrUtil.isEmpty(fileName)) {
throw new IllegalArgumentException("fileName:不能获取到文件名");
}
long fileSize = multiFile.getSize();
if (fileSize <= 0) {
throw new IllegalArgumentException("fileSize:文件内容为空");
}
// 文件名后缀
if (this.fileExt != null) {
String checkName = FileUtil.extName(fileName);
boolean find = false;
for (String ext : this.fileExt) {
find = StrUtil.equalsIgnoreCase(checkName, ext);
if (find) {
break;
}
}
if (!find) {
throw new IllegalArgumentException("fileExt:类型错误:" + checkName);
}
}
// 文件大小
if (maxSize > 0 && fileSize > maxSize) {
throw new IllegalArgumentException("maxSize:too big:" + fileSize + ">" + maxSize);
}
// 文件流类型
if (this.inputStreamType != null) {
InputStream inputStream = multiFile.getInputStream();
String fileType = FileTypeUtil.getType(inputStream);
if (!ArrayUtil.containsIgnoreCase(this.inputStreamType, fileType)) {
throw new IllegalArgumentException("inputStreamType:类型错误:" + fileType);
}
}
// 保存路径
String localPath;
if (this.savePath != null) {
localPath = this.savePath;
} else {
localPath = MultipartFileConfig.getFileTempPath();
}
// 保存的文件名
String filePath;
if (useOriginalFilename) {
filePath = FileUtil.normalize(String.format("%s/%s", localPath, fileName));
} else {
// 防止中文乱码
String saveFileName = UnicodeUtil.toUnicode(fileName);
saveFileName = saveFileName.replace(StrUtil.BACKSLASH, "_");
// 生成唯一id
filePath = FileUtil.normalize(String.format("%s/%s_%s", localPath, IdUtil.objectId(), saveFileName));
}
FileUtil.writeFromStream(multiFile.getInputStream(), filePath);
// 文件contentType
if (this.contentTypePrefix != null) {
// Path source = Paths.get(filePath);
String contentType = FileUtil.getMimeType(filePath);
//Files.probeContentType(source);
if (contentType == null) {
// 自动清理文件
FileUtil.del(filePath);
throw new IllegalArgumentException("contentTypePrefix:获取文件类型失败");
}
if (!contentType.startsWith(contentTypePrefix)) {
// 自动清理文件
FileUtil.del(filePath);
throw new IllegalArgumentException("contentTypePrefix:文件类型不正确:" + contentType);
}
}
return new String[]{filePath, fileName};
}
public MultipartFileBuilder(MultipartHttpServletRequest multipartHttpServletRequest) {
Objects.requireNonNull(multipartHttpServletRequest);
this.multipartHttpServletRequest = multipartHttpServletRequest;
}
}

View File

@ -20,7 +20,7 @@
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package org.dromara.jpom.common;
package org.dromara.jpom.exception;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.exceptions.ValidateException;

View File

@ -20,7 +20,7 @@
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
package org.dromara.jpom.common;
package org.dromara.jpom.exception;
/**
* @author bwcx_jzy

View File

@ -0,0 +1,33 @@
/*
* The MIT License (MIT)
*
* Copyright (c) 2019 Code Technology Studio
*
* 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:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* 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.
*/
package org.dromara.jpom.exception;
/**
* @author bwcx_jzy
* @since 24/1/4 004
*/
public class LogRecorderCloseException extends IllegalStateException {
public LogRecorderCloseException() {
super("日志记录器被关闭/或者未启用");
}
}

View File

@ -90,6 +90,10 @@ public class EnvironmentMapBuilder {
Map<String, String> map = new LinkedHashMap<>(this.map.size());
for (Map.Entry<String, Item> entry : this.map.entrySet()) {
Item entryValue = entry.getValue();
if (entryValue.value == null) {
// 值不能为 null
continue;
}
map.put(entry.getKey(), entryValue.value);
}
Optional.ofNullable(appendMap).ifPresent(objectMap -> {

View File

@ -29,28 +29,32 @@ package org.dromara.jpom.model;
* @since 2019/4/22
*/
public enum RunMode {
/**
* java -classpath
*/
ClassPath,
/**
* java -jar
*/
Jar,
/**
* java -jar Springboot war
*/
JarWar,
/**
* java -Djava.ext.dirs=lib -cp conf:run.jar $MAIN_CLASS
*/
JavaExtDirsCp,
/**
* 纯文件管理
*/
File,
/**
* 自定义项目管理
*/
Dsl,
/**
* java -classpath
*/
ClassPath,
/**
* java -jar
*/
Jar,
/**
* java -jar Springboot war
*/
JarWar,
/**
* java -Djava.ext.dirs=lib -cp conf:run.jar $MAIN_CLASS
*/
JavaExtDirsCp,
/**
* 纯文件管理
*/
File,
/**
* 自定义项目管理
*/
Dsl,
/**
* 软链
*/
Link,
}

View File

@ -29,12 +29,11 @@ import cn.hutool.core.text.StrSplitter;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.ReUtil;
import cn.hutool.core.util.StrUtil;
import cn.keepbx.jpom.model.BaseJsonModel;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.extern.slf4j.Slf4j;
import org.dromara.jpom.model.BaseModel;
import org.dromara.jpom.system.ExtConfigBean;
import org.dromara.jpom.util.FileUtils;
import org.springframework.util.Assert;
import java.io.File;
@ -46,7 +45,7 @@ import java.util.Set;
import java.util.stream.Collectors;
/**
* 白名单
* 授权
*
* @author bwcx_jzy
* @since 2019/4/16
@ -54,9 +53,9 @@ import java.util.stream.Collectors;
@Slf4j
@Data
@EqualsAndHashCode(callSuper = true)
public class AgentWhitelist extends BaseJsonModel {
public class AgentWhitelist extends BaseModel {
/**
* 项目目录白名单日志文件白名单
* 项目目录授权日志文件授权
*/
private List<String> project;
/**
@ -64,23 +63,6 @@ public class AgentWhitelist extends BaseJsonModel {
*/
private List<String> allowEditSuffix;
public static String convertRealPath(String path) {
String val = String.format("/%s/", path);
return FileUtil.normalize(val);
}
public static List<String> useConvert(List<String> list) {
if (list == null) {
return null;
}
return list.stream().map(AgentWhitelist::convertRealPath).collect(Collectors.toList());
}
public List<String> project() {
return useConvert(project);
}
/**
* 格式化判断是否与jpom 数据路径冲突
*
@ -88,16 +70,32 @@ public class AgentWhitelist extends BaseJsonModel {
* @return null 是有冲突的
*/
public static List<String> covertToArray(List<String> list, String errorMsg) {
return covertToArray(list, -1, errorMsg);
}
/**
* 格式化判断是否与jpom 数据路径冲突
*
* @param list list
* @return null 是有冲突的
*/
public static List<String> covertToArray(List<String> list, int maxLen, String errorMsg) {
if (list == null) {
return null;
}
return list.stream()
.map(s -> {
String val = String.format("/%s/", s);
val = FileUtil.normalize(val);
FileUtils.checkSlip(val);
String val = FileUtil.normalize(s);
Assert.state(FileUtil.isAbsolutePath(val), "需要配置绝对路径:" + val);
File file = FileUtil.file(val);
File parentFile = file.getParentFile();
Assert.notNull(parentFile, "不能配置根路径:" + val);
// 判断是否保护jpom 路径
Assert.state(!StrUtil.startWith(ExtConfigBean.getPath(), val), errorMsg);
//
if (maxLen > 0) {
Assert.state(StrUtil.length(val) <= maxLen, "配置路径超过" + maxLen + "长度限制:" + val);
}
return val;
})
.distinct()
@ -115,7 +113,7 @@ public class AgentWhitelist extends BaseJsonModel {
}
/**
* 判断是否在白名单列表中
* 判断是否在授权列表中
*
* @param list list
* @param path 对应项
@ -128,7 +126,7 @@ public class AgentWhitelist extends BaseJsonModel {
if (StrUtil.isEmpty(path)) {
return false;
}
File file1, file2 = FileUtil.file(convertRealPath(path));
File file1, file2 = FileUtil.file(path);
for (String item : list) {
file1 = FileUtil.file(item);
if (FileUtil.pathEquals(file1, file2)) {
@ -234,4 +232,42 @@ public class AgentWhitelist extends BaseJsonModel {
}
return null;
}
/**
* 检查授权包含关系
*
* @param jsonArray 要检查的对象
* @return null 正常
*/
public static String findStartsWith(List<String> jsonArray) {
return findStartsWith(jsonArray, 0);
}
/**
* 检查授权包含关系
*
* @param jsonArray 要检查的对象
* @param start 检查的坐标
* @return null 正常
*/
private static String findStartsWith(List<String> jsonArray, int start) {
if (jsonArray == null) {
return null;
}
String str = jsonArray.get(start);
int len = jsonArray.size();
for (int i = 0; i < len; i++) {
if (i == start) {
continue;
}
String findStr = jsonArray.get(i);
if (FileUtil.isSub(FileUtil.file(findStr), FileUtil.file(str))) {
return str;
}
}
if (start < len - 1) {
return findStartsWith(jsonArray, start + 1);
}
return null;
}
}

View File

@ -30,6 +30,7 @@ import cn.hutool.core.io.IoUtil;
import cn.keepbx.jpom.log.ILogRecorder;
import org.dromara.jpom.JpomApplication;
import org.dromara.jpom.common.Const;
import org.dromara.jpom.util.CommandUtil;
import org.dromara.jpom.util.LogRecorder;
import java.io.File;
@ -55,8 +56,13 @@ public abstract class BaseRunScript implements AutoCloseable, ILogRecorder {
protected InputStream inputStream;
protected BaseRunScript(File logFile, Charset charset) {
this.logFile = logFile;
this.logRecorder = LogRecorder.builder().file(logFile).charset(charset).build();
if (logFile == null) {
this.logFile = null;
this.logRecorder = null;
} else {
this.logFile = logFile;
this.logRecorder = LogRecorder.builder().file(logFile).charset(charset).build();
}
}
@Override
@ -105,7 +111,8 @@ public abstract class BaseRunScript implements AutoCloseable, ILogRecorder {
public void close() {
// windows 中不能正常关闭
IoUtil.close(inputStream);
Optional.ofNullable(process).ifPresent(Process::destroy);
CommandUtil.kill(process);
IoUtil.close(logRecorder);
}
/**

View File

@ -22,30 +22,46 @@
*/
package org.dromara.jpom.socket;
import lombok.Getter;
/**
* 控制台socket 操作枚举
*
* @author bwcx_jzy
* @since 2019/4/16
*/
@Getter
public enum ConsoleCommandOp {
/**
* 启动
*/
start,
stop,
restart,
start(true),
stop(true),
restart(true),
status,
/**
* 重载
*/
reload(true),
/**
* 运行日志
*/
showlog,
/**
* 查看内存信息
*/
top,
/**
* 心跳
*/
heart
heart,
;
/**
* 是否支持手动操作执行
*/
private final boolean canOpt;
ConsoleCommandOp() {
this.canOpt = false;
}
ConsoleCommandOp(boolean canOpt) {
this.canOpt = canOpt;
}
}

View File

@ -58,10 +58,8 @@ import java.util.function.Function;
*/
@Slf4j
public class ExtConfigBean {
/**
*
* 控制台日志编码
*/
private static Charset consoleLogCharset;

View File

@ -23,6 +23,7 @@
package org.dromara.jpom.util;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.LineHandler;
@ -38,6 +39,7 @@ import java.io.InputStream;
import java.nio.charset.Charset;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.function.BiConsumer;
/**
@ -172,8 +174,13 @@ public class CommandUtil {
String[] cmd = commands.toArray(new String[]{});
result = exec(cmd, file);
} catch (Exception e) {
log.error("执行命令异常", e);
result += e.getMessage();
if (ExceptionUtil.isCausedBy(e, InterruptedException.class)) {
log.warn("执行被中断:{}", command);
result += "执行被中断";
} else {
log.error("执行命令异常", e);
result += e.getMessage();
}
}
return result;
}
@ -266,7 +273,7 @@ public class CommandUtil {
* @param command 命令
* @throws IOException 异常
*/
public static void asyncExeLocalCommand(File file, String command) throws Exception {
public static void asyncExeLocalCommand(String command, File file) throws Exception {
String newCommand = StrUtil.replace(command, StrUtil.CRLF, StrUtil.SPACE);
newCommand = StrUtil.replace(newCommand, StrUtil.LF, StrUtil.SPACE);
//
@ -350,6 +357,18 @@ public class CommandUtil {
return false;
}
/**
* 执行脚本
*
* @param scriptFile 脚本文件
* @param baseDir 基础路径
* @param env 环境变量
* @param args 参数
* @param consumer 回调
* @return 退出码
* @throws IOException io
* @throws InterruptedException 异常
*/
public static int execWaitFor(File scriptFile, File baseDir, Map<String, String> env, String args, BiConsumer<String, Process> consumer) throws IOException, InterruptedException {
ProcessBuilder processBuilder = new ProcessBuilder();
//
@ -362,11 +381,13 @@ public class CommandUtil {
processBuilder.command(command);
Optional.ofNullable(baseDir).ifPresent(processBuilder::directory);
Map<String, String> environment = processBuilder.environment();
// 新增逻辑,将env和environment里value==null替换成空字符,防止putAll出现空指针报错
env.replaceAll((k, v) -> Optional.ofNullable(v).orElse(StrUtil.EMPTY));
environment.replaceAll((k, v) -> Optional.ofNullable(v).orElse(StrUtil.EMPTY));
// 环境变量
Optional.ofNullable(env).ifPresent(environment::putAll);
// 新增逻辑,将env和environment里value==null替换成空字符,防止putAll出现空指针报错
if (env != null) {
// 环境变量
env.replaceAll((k, v) -> Optional.ofNullable(v).orElse(StrUtil.EMPTY));
environment.putAll(env);
}
//
Process process = processBuilder.start();
try (InputStream inputStream = process.getInputStream()) {
@ -374,4 +395,35 @@ public class CommandUtil {
}
return process.waitFor();
}
/**
* 关闭 Process实例
*
* @param process Process
*/
public static void kill(Process process) {
if (process == null) {
return;
}
while (true) {
Object handle = tryGetProcessId(process);
process.destroy();
if (process.isAlive()) {
process.destroyForcibly();
try {
process.waitFor(500, TimeUnit.MILLISECONDS);
} catch (InterruptedException ignored) {
}
log.info("等待关闭[Process]进程:{}", handle);
} else {
break;
}
}
}
public static Object tryGetProcessId(Process process) {
Object handle = ReflectUtil.getFieldValue(process, "handle");
Object pid = ReflectUtil.getFieldValue(process, "pid");
return Optional.ofNullable(handle).orElse(pid);
}
}

View File

@ -30,7 +30,6 @@ import org.dromara.jpom.common.JpomManifest;
import java.util.Arrays;
import java.util.List;
import java.util.Optional;
import java.util.function.Supplier;
import java.util.stream.Collectors;
@ -56,9 +55,9 @@ public class JvmUtil {
};
/**
* 持的标签数组
* 持的标签数组
*/
private static final String[] JPOM_PID_TAG = new String[]{"DJpom.application", "Jpom.application", "Dapplication"};
private static final String[] JPOM_PID_TAG = new String[]{"DJpom.application", "Jpom.application"};
/**
* 检查 jps 命令是否正常
@ -98,10 +97,13 @@ public class JvmUtil {
public static boolean exist(long pid) {
String execSystemCommand = CommandUtil.execSystemCommand("jps -l");
List<String> list = StrSplitter.splitTrim(execSystemCommand, StrUtil.LF, true);
String pidCommandInfo = list.stream().filter(s -> {
List<String> split = StrSplitter.splitTrim(s, StrUtil.SPACE, true);
return StrUtil.equals(pid + "", CollUtil.getFirst(split));
}).findAny().orElse(null);
String pidCommandInfo = list.stream()
.filter(s -> {
List<String> split = StrSplitter.splitTrim(s, StrUtil.SPACE, true);
return StrUtil.equals(pid + "", CollUtil.getFirst(split));
})
.findAny()
.orElse(null);
return StrUtil.isNotEmpty(pidCommandInfo);
}
@ -114,11 +116,15 @@ public class JvmUtil {
public static Integer getPidByTag(String tag) {
String execSystemCommand = CommandUtil.execSystemCommand("jps -mv");
List<String> list = StrSplitter.splitTrim(execSystemCommand, StrUtil.LF, true);
Optional<String> any = list.stream().filter(s -> checkCommandLineIsJpom(s, tag)).map(s -> {
List<String> split = StrUtil.split(s, StrUtil.SPACE);
return CollUtil.getFirst(split);
}).findAny();
return any.map(Convert::toInt).orElse(null);
return list.stream()
.filter(s -> checkCommandLineIsJpom(s, tag))
.map(s -> {
List<String> split = StrUtil.split(s, StrUtil.SPACE);
return CollUtil.getFirst(split);
})
.findAny()
.map(Convert::toInt)
.orElse(null);
}
/**

View File

@ -22,17 +22,17 @@
*/
package org.dromara.jpom.util;
import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.io.IoUtil;
import cn.hutool.core.io.file.FileWriter;
import cn.hutool.core.util.CharsetUtil;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil;
import cn.keepbx.jpom.log.ILogRecorder;
import lombok.Builder;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.dromara.jpom.exception.LogRecorderCloseException;
import org.springframework.util.Assert;
import java.io.File;
import java.io.PrintWriter;
@ -44,23 +44,54 @@ import java.nio.charset.Charset;
* @author bwcx_jzy
* @since 2022/1/26
*/
@Builder
@Slf4j
@Getter
public class LogRecorder implements ILogRecorder {
/**
* 文件
*/
private File file;
/**
* 文件编码
*/
private Charset charset;
public class LogRecorder implements ILogRecorder, AutoCloseable {
public Charset getCharset() {
return ObjectUtil.defaultIfNull(this.charset, CharsetUtil.CHARSET_UTF_8);
private File file;
private PrintWriter writer;
public LogRecorder(File file, Charset charset) {
if (file == null) {
this.writer = null;
this.file = null;
return;
}
this.file = file;
this.writer = FileWriter.create(file, charset).getPrintWriter(true);
}
public static Builder builder() {
return new Builder();
}
public static class Builder {
private File file;
private Charset charset;
Builder() {
}
public Builder file(final File file) {
this.file = file;
return this;
}
public Builder charset(final Charset charset) {
this.charset = charset;
return this;
}
public LogRecorder build() {
Charset charset1 = ObjectUtil.defaultIfNull(this.charset, CharsetUtil.CHARSET_UTF_8);
return new LogRecorder(this.file, charset1);
}
public String toString() {
return "LogRecorder.LogRecorderBuilder(file=" + this.file + ", charset=" + this.charset + ")";
}
}
/**
* 记录错误信息
*
@ -69,9 +100,13 @@ public class LogRecorder implements ILogRecorder {
*/
public void error(String title, Throwable throwable) {
log.error(title, throwable);
FileUtil.appendLines(CollectionUtil.toList(title), this.file, this.getCharset());
if (writer == null) {
throw new LogRecorderCloseException();
}
writer.println(title);
String s = ExceptionUtil.stacktraceToString(throwable);
FileUtil.appendLines(CollectionUtil.toList(s), this.file, this.getCharset());
writer.println(s);
writer.flush();
}
/**
@ -80,8 +115,12 @@ public class LogRecorder implements ILogRecorder {
* @param info 日志
*/
public String info(String info, Object... vals) {
if (writer == null) {
throw new LogRecorderCloseException();
}
String format = StrUtil.format(info, vals);
FileUtil.appendLines(CollectionUtil.toList(format), this.file, this.getCharset());
writer.println(format);
writer.flush();
return format;
}
@ -118,8 +157,12 @@ public class LogRecorder implements ILogRecorder {
* @param info 日志
*/
public void append(String info, Object... vals) {
String format = StrUtil.format(info, vals);
FileUtil.appendString(format, this.file, this.getCharset());
if (writer == null) {
throw new LogRecorderCloseException();
}
writer.append(StrUtil.format(info, vals));
writer.flush();
}
/**
@ -128,6 +171,18 @@ public class LogRecorder implements ILogRecorder {
* @return Writer
*/
public PrintWriter getPrintWriter() {
return FileWriter.create(this.file, this.getCharset()).getPrintWriter(true);
return writer;
}
@Override
public void close() {
IoUtil.close(writer);
this.writer = null;
this.file = null;
}
public long size() {
Assert.notNull(writer, "日志记录器未启用");
return FileUtil.size(this.file);
}
}

View File

@ -256,15 +256,17 @@ public class StringUtil {
return MapUtil.newHashMap();
}
List<Tuple> collect = envStrList.stream()
.map(StrUtil::trim)
.filter(s -> !StrUtil.isEmpty(s) && !StrUtil.startWith(s, "#"))
.map(s -> {
List<String> list1 = StrUtil.splitTrim(s, "=");
if (CollUtil.size(list1) != 2) {
return null;
}
return new Tuple(list1.get(0), list1.get(1));
}).filter(Objects::nonNull).collect(Collectors.toList());
.map(StrUtil::trim)
.filter(s -> !StrUtil.isEmpty(s) && !StrUtil.startWith(s, "#"))
.map(s -> {
List<String> list1 = StrUtil.splitTrim(s, "=");
if (CollUtil.size(list1) != 2) {
return null;
}
return new Tuple(list1.get(0), list1.get(1));
})
.filter(Objects::nonNull)
.collect(Collectors.toList());
return CollStreamUtil.toMap(collect, objects -> objects.get(0), objects -> objects.get(1));
}

View File

@ -7,5 +7,5 @@
| |
|_|
➜ Jpom \ (•◡•) / (v2.10.47)
➜ Jpom \ (•◡•) / (v2.11.0.8)

View File

@ -34,7 +34,7 @@ ARG JPOM_VERSION
ENV USE_JPOM_VERSION=${JPOM_VERSION}${TEMP_VERSION}
RUN --mount=type=cache,target=/root/.m2 bash ./script/replaceVersion.sh "${USE_JPOM_VERSION}" "release"
ENV NODE_VERSION 16.13.1
ENV NODE_VERSION 18.19.0
RUN set -eux; \
ARCH="$(dpkg --print-architecture)"; \

View File

@ -29,7 +29,7 @@ LABEL maintainer="bwcx-jzy <bwcx_jzy@163.com>"
LABEL documentation="https://jpom.top"
ENV JPOM_HOME /usr/local/jpom-server
ENV JPOM_PKG_VERSION 2.10.47.7
ENV JPOM_PKG_VERSION 2.11.0.8
ENV JPOM_PKG server-${JPOM_PKG_VERSION}-release.tar.gz
ENV SHA1_NAME server-${JPOM_PKG_VERSION}-release.tar.gz.sha1

View File

@ -29,7 +29,7 @@ LABEL maintainer="bwcx-jzy <bwcx_jzy@163.com>"
LABEL documentation="https://jpom.top"
ENV JPOM_HOME /usr/local/jpom-server
ENV JPOM_PKG_VERSION 2.10.47.7
ENV JPOM_PKG_VERSION 2.11.0.8
ENV JPOM_PKG server-${JPOM_PKG_VERSION}-release.tar.gz
ENV SHA1_NAME server-${JPOM_PKG_VERSION}-release.tar.gz.sha1

View File

@ -29,13 +29,13 @@
<parent>
<artifactId>jpom-parent</artifactId>
<groupId>org.dromara.jpom</groupId>
<version>2.10.47</version>
<version>2.11.0.8</version>
<relativePath>../../pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>
<name>Jpom Server</name>
<artifactId>server</artifactId>
<version>2.10.47</version>
<version>2.11.0.8</version>
<properties>
<start-class>org.dromara.jpom.JpomServerApplication</start-class>
</properties>
@ -101,6 +101,11 @@
<artifactId>hutool-captcha</artifactId>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>

View File

@ -53,7 +53,7 @@ public class JpomServerApplication {
/**
* 启动执行
* <p>
* --rest:ip_config 重置 IP 白名单配置
* --rest:ip_config 重置 IP 授权配置
* <p>
* --rest:load_init_db 重新加载数据库初始化操作
* <p>
@ -80,7 +80,7 @@ public class JpomServerApplication {
SpringApplicationBuilder springApplicationBuilder = new SpringApplicationBuilder(JpomServerApplication.class);
springApplicationBuilder.bannerMode(Banner.Mode.LOG);
springApplicationBuilder.run(args);
// 重置 ip 白名单配置
// 重置 ip 授权配置
if (ArrayUtil.containsIgnoreCase(args, "--rest:ip_config")) {
SystemParametersServer parametersServer = SpringUtil.getBean(SystemParametersServer.class);
parametersServer.delByKey(SystemIpConfigModel.ID);

View File

@ -44,6 +44,7 @@ import lombok.extern.slf4j.Slf4j;
import org.dromara.jpom.JpomApplication;
import org.dromara.jpom.common.BaseServerController;
import org.dromara.jpom.common.ServerConst;
import org.dromara.jpom.exception.LogRecorderCloseException;
import org.dromara.jpom.func.assets.server.MachineDockerServer;
import org.dromara.jpom.func.files.service.FileStorageService;
import org.dromara.jpom.model.data.BuildInfoModel;
@ -68,6 +69,7 @@ import org.dromara.jpom.util.AntPathUtil;
import org.dromara.jpom.util.CommandUtil;
import org.dromara.jpom.util.FileUtils;
import org.dromara.jpom.util.LogRecorder;
import org.dromara.jpom.webhook.DefaultWebhookPluginImpl;
import org.springframework.util.Assert;
import java.io.File;
@ -115,6 +117,7 @@ public class BuildExecuteManage implements Runnable {
private LogRecorder logRecorder;
private File gitFile;
private Thread currentThread;
private ReleaseManage releaseManage;
/**
* 提交任务时间
@ -197,7 +200,7 @@ public class BuildExecuteManage implements Runnable {
* 取消任务
*/
private void cancelTask(String desc) {
Optional.ofNullable(process).ifPresent(Process::destroy);
CommandUtil.kill(process);
Integer buildMode = taskData.buildInfoModel.getBuildMode();
if (buildMode != null && buildMode == 1) {
// 容器构建 删除容器
@ -220,6 +223,7 @@ public class BuildExecuteManage implements Runnable {
buildExecuteService.updateStatus(buildId, logId, taskData.buildInfoModel.getBuildId(), BuildStatus.Cancel, desc);
Optional.ofNullable(currentThread).ifPresent(Thread::interrupt);
BUILD_MANAGE_MAP.remove(buildId);
IoUtil.close(logRecorder);
}
/**
@ -556,7 +560,7 @@ public class BuildExecuteManage implements Runnable {
String buildInfoModelId = buildInfoModel.getId();
taskData.buildContainerId = "jpom-build-" + buildInfoModelId;
map.put("dockerName", taskData.buildContainerId);
map.put("logFile", logRecorder.getFile());
map.put("logRecorder", logRecorder);
//
List<String> copy = ObjectUtil.defaultIfNull(dockerYmlDsl.getCopy(), new ArrayList<>());
// 将仓库文件上传到容器
@ -626,11 +630,15 @@ public class BuildExecuteManage implements Runnable {
int waitFor = JpomApplication.getInstance()
.execScript(s1 + script, file -> {
try {
return CommandUtil.execWaitFor(file, this.gitFile, environment, StrUtil.EMPTY, (s, process) -> logRecorder.info(s));
return CommandUtil.execWaitFor(file, this.gitFile, environment, StrUtil.EMPTY, (s, process) -> {
BuildExecuteManage.this.process = process;
logRecorder.info(s);
});
} catch (IOException | InterruptedException e) {
throw Lombok.sneakyThrow(e);
}
});
BuildExecuteManage.this.process = null;
logRecorder.system("执行脚本的退出码是:{}", waitFor);
// 判断是否为严格执行
if (buildExtraModule.strictlyEnforce()) {
@ -652,7 +660,7 @@ public class BuildExecuteManage implements Runnable {
BuildInfoModel buildInfoModel = taskData.buildInfoModel;
UserModel userModel = taskData.userModel;
// 发布文件
ReleaseManage releaseManage = ReleaseManage.builder()
this.releaseManage = ReleaseManage.builder()
.buildNumberId(buildInfoModel.getBuildId())
.buildExtraModule(buildExtraModule)
.userModel(userModel)
@ -781,9 +789,7 @@ public class BuildExecuteManage implements Runnable {
File historyPackageZipFile = BuildUtil.getHistoryPackageZipFile(buildExtraModule.getId(), buildInfoModel1.getBuildId());
CommandUtil.systemFastDel(historyPackageZipFile);
// 计算文件占用大小
File file = logRecorder.getFile();
long size = FileUtil.size(file);
long size = logRecorder.size();
BuildHistoryLog buildInfoModel = new BuildHistoryLog();
buildInfoModel.setId(logId);
buildInfoModel.setResultFileSize(taskData.resultFileSize);
@ -850,6 +856,8 @@ public class BuildExecuteManage implements Runnable {
if (!stop) { // 没有执行 stop
this.asyncWebHooks("success");
}
} catch (LogRecorderCloseException logRecorderCloseException) {
log.warn("构建日志记录器已关闭,可能手动取消停止构建,流程:{}", processName);
} catch (DiyInterruptException diyInterruptException) {
// 主动中断
this.asyncWebHooks("stop", "process", processName);
@ -866,6 +874,7 @@ public class BuildExecuteManage implements Runnable {
this.clearResources();
logRecorder.system("构建结束-累计耗时:{}", DateUtil.formatBetween(SystemClock.now() - startTime));
this.asyncWebHooks("done");
IoUtil.close(logRecorder);
BaseServerController.removeAll();
}
}
@ -913,6 +922,7 @@ public class BuildExecuteManage implements Runnable {
ThreadUtil.execute(() -> {
try {
IPlugin plugin = PluginFactory.getPlugin("webhook");
map.put("JPOM_WEBHOOK_EVENT", DefaultWebhookPluginImpl.WebhookEvent.BUILD);
plugin.execute(s, map);
} catch (Exception e) {
log.error("WebHooks 调用错误", e);
@ -1017,6 +1027,7 @@ public class BuildExecuteManage implements Runnable {
FileUtil.del(scriptFile);
} catch (Exception ignored) {
}
IoUtil.close(scriptLog);
}
}

View File

@ -222,6 +222,8 @@ public class BuildExecuteService {
//
EnvironmentMapBuilder environmentMapBuilder = buildHistoryLog.toEnvironmentMapBuilder();
//
File logFile = BuildUtil.getLogFile(item.getId(), buildId);
LogRecorder logRecorder = LogRecorder.builder().file(logFile).build();
ReleaseManage manage = ReleaseManage.builder()
.buildExtraModule(buildExtraModule)
.logId(buildHistoryLog.getId())
@ -233,10 +235,9 @@ public class BuildExecuteService {
.buildNumberId(buildHistoryLog.getBuildNumberId())
.fromBuildNumberId(fromBuildNumberId)
.buildExecuteService(this)
.logRecorder(logRecorder)
.buildEnv(environmentMapBuilder)
.build();
File logFile = BuildUtil.getLogFile(item.getId(), buildId);
LogRecorder logRecorder = LogRecorder.builder().file(logFile).build();
//
logRecorder.system("开始准备回滚:{} -> {}", fromBuildNumberId, buildId);
//

View File

@ -116,8 +116,9 @@ public class ReleaseManage {
private final BuildExtConfig buildExtConfig;
private EnvironmentMapBuilder buildEnv;
private LogRecorder logRecorder;
private final LogRecorder logRecorder;
private File resultFile;
private Process process;
private Integer getRealBuildNumberId() {
@ -125,11 +126,11 @@ public class ReleaseManage {
}
private void init() {
if (this.logRecorder == null) {
// 回滚的时候需要重新创建对象
File logFile = BuildUtil.getLogFile(buildExtraModule.getId(), this.buildNumberId);
this.logRecorder = LogRecorder.builder().file(logFile).build();
}
// if (this.logRecorder == null) {
// // 回滚的时候需要重新创建对象
// File logFile = BuildUtil.getLogFile(buildExtraModule.getId(), this.buildNumberId);
// this.logRecorder = LogRecorder.builder().file(logFile).build();
// }
Assert.notNull(buildEnv, "没有找到任何环境变量");
}
@ -305,7 +306,7 @@ public class ReleaseManage {
logRecorder.system("start push to repository in({}),{} {}{}", map.get("name"), StrUtil.emptyToDefault((String) map.get("registryUrl"), StrUtil.EMPTY), repositoryItem, System.lineSeparator());
//
map.put("repository", repositoryItem);
Consumer<String> logConsumer = s -> logRecorder.info(s);
Consumer<String> logConsumer = logRecorder::info;
map.put("logConsumer", logConsumer);
IPlugin plugin = PluginFactory.getPlugin(DockerInfoService.DOCKER_PLUGIN_NAME);
try {
@ -355,13 +356,14 @@ public class ReleaseManage {
map.put("noCache", extraModule.getDockerNoCache());
map.put("labels", extraModule.getDockerImagesLabels());
map.put("env", envMap);
Consumer<String> logConsumer = s -> logRecorder.append(s);
Consumer<String> logConsumer = logRecorder::append;
map.put("logConsumer", logConsumer);
IPlugin plugin = PluginFactory.getPlugin(DockerInfoService.DOCKER_PLUGIN_NAME);
try {
return (boolean) plugin.execute("buildImage", map);
} catch (Exception e) {
logRecorder.systemError("构建镜像调用容器异常", e);
log.error("构建镜像调用容器异常", e);
logRecorder.error("构建镜像调用容器异常", e);
return false;
}
}
@ -386,11 +388,15 @@ public class ReleaseManage {
int waitFor = JpomApplication.getInstance()
.execScript(s1 + releaseCommand, file -> {
try {
return CommandUtil.execWaitFor(file, sourceFile, envFileMap, StrUtil.EMPTY, (s, process) -> logRecorder.info(s));
return CommandUtil.execWaitFor(file, sourceFile, envFileMap, StrUtil.EMPTY, (s, process) -> {
ReleaseManage.this.process = process;
logRecorder.info(s);
});
} catch (IOException | InterruptedException e) {
throw Lombok.sneakyThrow(e);
}
});
ReleaseManage.this.process = null;
logRecorder.system("执行发布脚本的退出码是:{}", waitFor);
// 判断是否为严格执行
if (buildExtraModule.strictlyEnforce()) {
@ -615,6 +621,8 @@ public class ReleaseManage {
.file(zipFile)
.logRecorder(logRecorder)
.userModel(userModel)
.mode("build-trigger")
.modeData(buildExtraModule.getId())
.unzip(unZip)
// 由构建配置决定是否删除
.doneDeleteFile(false)
@ -638,7 +646,7 @@ public class ReleaseManage {
BaseServerController.resetInfo(userModel);
this.init();
//
buildEnv.eachStr(s -> logRecorder.system(s));
buildEnv.eachStr(logRecorder::system);
logRecorder.system("开始回滚:{}", DateTime.now());
//
String errorMsg = this.start(null, item);
@ -652,6 +660,8 @@ public class ReleaseManage {
log.error("执行发布异常", e);
logRecorder.error("执行发布异常", e);
this.updateStatus(BuildStatus.PubError, e.getMessage());
} finally {
IoUtil.close(this.logRecorder);
}
}
}

View File

@ -71,11 +71,8 @@ public abstract class BaseServerController extends BaseJpomController {
}
protected NodeModel tryGetNode() {
String nodeId = getParameter(NODE_ID);
if (StrUtil.isEmpty(nodeId)) {
return null;
}
return nodeService.getByKey(nodeId);
HttpServletRequest request = getRequest();
return (NodeModel) request.getAttribute("node");
}
/**
@ -97,6 +94,29 @@ public abstract class BaseServerController extends BaseJpomController {
return null;
}
/**
* 判断是否传入机器 id 或者节点id
*
* @param machineId 机器id
* @param request 请求
* @param nodeUrl 节点 url
* @param pars 参数
* @param <T> 泛型
* @return data
*/
protected <T> JsonMessage<T> tryRequestNode(String machineId, HttpServletRequest request, NodeUrl nodeUrl, String... pars) {
NodeModel nodeModel = tryGetNode();
if (nodeModel != null) {
return NodeForward.request(nodeModel, request, nodeUrl, new String[]{}, pars);
}
if (StrUtil.isNotEmpty(machineId)) {
MachineNodeModel model = machineNodeServer.getByKey(machineId);
Assert.notNull(model, "没有找到对应的机器");
return NodeForward.request(model, request, nodeUrl, new String[]{}, pars);
}
return null;
}
/**
* 验证 cron 表达式, demo 账号不能开启 cron
*

View File

@ -25,8 +25,11 @@ package org.dromara.jpom.common;
import cn.keepbx.jpom.IJsonMessage;
import cn.keepbx.jpom.model.JsonMessage;
import lombok.extern.slf4j.Slf4j;
import org.dromara.jpom.system.AgentException;
import org.dromara.jpom.system.AuthorizeException;
import org.dromara.jpom.exception.AgentAuthorizeException;
import org.dromara.jpom.exception.AgentException;
import org.dromara.jpom.exception.BaseExceptionHandler;
import org.dromara.jpom.exception.PermissionException;
import org.dromara.jpom.transport.TransportAgentException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
@ -47,8 +50,8 @@ public class GlobalDefaultExceptionHandler extends BaseExceptionHandler {
*
* @param e 异常
*/
@ExceptionHandler({AuthorizeException.class})
public IJsonMessage<String> delExceptionHandler(AuthorizeException e) {
@ExceptionHandler({AgentAuthorizeException.class})
public IJsonMessage<String> delExceptionHandler(AgentAuthorizeException e) {
return e.getJsonMessage();
}
@ -62,7 +65,7 @@ public class GlobalDefaultExceptionHandler extends BaseExceptionHandler {
* @author jzy
* @since 2021-08-01
*/
@ExceptionHandler({AgentException.class})
@ExceptionHandler({AgentException.class, TransportAgentException.class})
public IJsonMessage<String> agentExceptionHandler(HttpServletRequest request, AgentException e) {
Throwable cause = e.getCause();
if (cause != null) {
@ -70,4 +73,15 @@ public class GlobalDefaultExceptionHandler extends BaseExceptionHandler {
}
return new JsonMessage<>(405, e.getMessage());
}
/**
* 权限异常 需要退出登录
*
* @param e 异常
* @return json
*/
@ExceptionHandler({PermissionException.class})
public IJsonMessage<String> doPermissionException(PermissionException e) {
return new JsonMessage<>(ServerConst.AUTHORIZE_TIME_OUT_CODE, e.getMessage());
}
}

View File

@ -75,4 +75,8 @@ public class ServerConst extends Const {
public static final String ACCOUNT_LOCKED_TIP = "账号已经被禁用,不能使用";
public static final String CHECK_SYSTEM = "check-system";
public static final String RSA = "RSA";
public static final String EC = "EC";
}

View File

@ -27,7 +27,7 @@ import cn.hutool.core.util.StrUtil;
import cn.keepbx.jpom.model.JsonMessage;
import com.alibaba.fastjson2.TypeReference;
import lombok.extern.slf4j.Slf4j;
import org.dromara.jpom.system.AgentException;
import org.dromara.jpom.exception.AgentException;
import org.dromara.jpom.transport.INodeInfo;
import org.dromara.jpom.transport.TransformServer;

View File

@ -44,12 +44,12 @@ import lombok.extern.slf4j.Slf4j;
import org.dromara.jpom.common.BaseServerController;
import org.dromara.jpom.common.Const;
import org.dromara.jpom.configuration.NodeConfig;
import org.dromara.jpom.exception.AgentAuthorizeException;
import org.dromara.jpom.exception.AgentException;
import org.dromara.jpom.func.assets.model.MachineNodeModel;
import org.dromara.jpom.func.assets.server.MachineNodeServer;
import org.dromara.jpom.model.data.NodeModel;
import org.dromara.jpom.model.user.UserModel;
import org.dromara.jpom.system.AgentException;
import org.dromara.jpom.system.AuthorizeException;
import org.dromara.jpom.system.ServerConfig;
import org.dromara.jpom.transport.*;
import org.dromara.jpom.util.StrictSyncFinisher;
@ -650,7 +650,7 @@ public class NodeForward {
if (data instanceof JsonMessage) {
JsonMessage<?> jsonMessage = (JsonMessage<?>) data;
if (jsonMessage.getCode() == Const.AUTHORIZE_ERROR) {
throw new AuthorizeException(new JsonMessage<>(jsonMessage.getCode(), jsonMessage.getMsg()));
throw new AgentAuthorizeException(new JsonMessage<>(jsonMessage.getCode(), jsonMessage.getMsg()));
}
} else {
throw new IllegalStateException("消息转换异常");

Some files were not shown because too many files have changed in this diff Show More