diff --git a/.workflow/MasterPipeline.yml b/.workflow/MasterPipeline.yml index b99ddcce8..ad079c8f1 100644 --- a/.workflow/MasterPipeline.yml +++ b/.workflow/MasterPipeline.yml @@ -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 .. diff --git a/CHANGELOG-BETA.md b/CHANGELOG-BETA.md index c5d0c6b5a..fa504e8d3 100644 --- a/CHANGELOG-BETA.md +++ b/CHANGELOG-BETA.md @@ -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 模式代替)如果您当前使用到此功能请先手动备份相关数据 -> 为什么要下架上述功能:由于版本迭代已经有更好的新功能可以代替之前旧功能,并且新功能从另一种角度更方便。下架也是为了我们后续版本维护迭代更高效 +>❓ 为什么要下架上述功能:由于版本迭代已经有更好的新功能可以代替之前旧功能,并且新功能从另一种角度更方便。下架也是为了我们后续版本维护迭代更高效 + + +- 【白名单】关键词统一调整为【授权】 +- 【黑名单】关键词统一调整为【禁止】 ------ diff --git a/README.md b/README.md index 7179e7c4f..2ffec8c1e 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,9 @@

🚀简而轻的低侵入式在线构建、自动部署、日常运维、项目监控软件

+

+ 更是一款原生 ops 软件 +

@@ -26,13 +29,7 @@ codacy - - jpom66 请备注jpom - -

- -

- + docker pull @@ -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) + +## 💖 周边商品 + +为了更好地维持开源项目,我们决定推出周边商品。 + +购买支持我们这样您既获得了一份小商品我们也获得了您购买商品的利润(周边商品的价格会比市场价稍高,介意请勿下单) + +

+shop home +

+ ## 🔨贡献指南 ### 贡献须知 @@ -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) - ## 🌍 知识星球

@@ -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: diff --git a/SECURITY.md b/SECURITY.md index 8330946a8..a2210d574 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -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: | diff --git a/env-beta.env b/env-beta.env index 1d5ef59f6..90a06f2a4 100644 --- a/env-beta.env +++ b/env-beta.env @@ -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 diff --git a/modules/agent-transport/agent-transport-common/pom.xml b/modules/agent-transport/agent-transport-common/pom.xml index 0de8bc99e..777fe4949 100644 --- a/modules/agent-transport/agent-transport-common/pom.xml +++ b/modules/agent-transport/agent-transport-common/pom.xml @@ -30,7 +30,7 @@ org.dromara.jpom.agent-transport jpom-agent-transport-parent - 2.10.47 + 2.11.0.8 ../pom.xml diff --git a/modules/agent-transport/agent-transport-common/src/main/java/org/dromara/jpom/transport/AgentException.java b/modules/agent-transport/agent-transport-common/src/main/java/org/dromara/jpom/transport/TransportAgentException.java similarity index 91% rename from modules/agent-transport/agent-transport-common/src/main/java/org/dromara/jpom/transport/AgentException.java rename to modules/agent-transport/agent-transport-common/src/main/java/org/dromara/jpom/transport/TransportAgentException.java index 45a494596..277a58f2c 100644 --- a/modules/agent-transport/agent-transport-common/src/main/java/org/dromara/jpom/transport/AgentException.java +++ b/modules/agent-transport/agent-transport-common/src/main/java/org/dromara/jpom/transport/TransportAgentException.java @@ -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); } } diff --git a/modules/agent-transport/agent-transport-http/pom.xml b/modules/agent-transport/agent-transport-http/pom.xml index e08e3297c..dc151254c 100644 --- a/modules/agent-transport/agent-transport-http/pom.xml +++ b/modules/agent-transport/agent-transport-http/pom.xml @@ -30,7 +30,7 @@ org.dromara.jpom.agent-transport jpom-agent-transport-parent - 2.10.47 + 2.11.0.8 ../pom.xml diff --git a/modules/agent-transport/agent-transport-http/src/main/java/org/dromara/jpom/transport/HttpTransportServer.java b/modules/agent-transport/agent-transport-http/src/main/java/org/dromara/jpom/transport/HttpTransportServer.java index e5c1bf5e7..f168f09c1 100644 --- a/modules/agent-transport/agent-transport-http/src/main/java/org/dromara/jpom/transport/HttpTransportServer.java +++ b/modules/agent-transport/agent-transport-http/src/main/java/org/dromara/jpom/transport/HttpTransportServer.java @@ -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; }); diff --git a/modules/agent-transport/pom.xml b/modules/agent-transport/pom.xml index 397f78f73..e0654ee6b 100644 --- a/modules/agent-transport/pom.xml +++ b/modules/agent-transport/pom.xml @@ -29,7 +29,7 @@ jpom-parent org.dromara.jpom - 2.10.47 + 2.11.0.8 ../../pom.xml pom @@ -38,7 +38,7 @@ agent-transport-http 4.0.0 - 2.10.47 + 2.11.0.8 org.dromara.jpom.agent-transport jpom-agent-transport-parent Jpom Agent Transport diff --git a/modules/agent/pom.xml b/modules/agent/pom.xml index fbf5dcc42..f6d11c5f0 100644 --- a/modules/agent/pom.xml +++ b/modules/agent/pom.xml @@ -29,12 +29,12 @@ jpom-parent org.dromara.jpom - 2.10.47 + 2.11.0.8 ../../pom.xml 4.0.0 agent - 2.10.47 + 2.11.0.8 Jpom Agent org.dromara.jpom.JpomAgentApplication diff --git a/modules/agent/src/main/java/org/dromara/jpom/common/AgentConst.java b/modules/agent/src/main/java/org/dromara/jpom/common/AgentConst.java index c0f3b3d65..02fd6131d 100644 --- a/modules/agent/src/main/java/org/dromara/jpom/common/AgentConst.java +++ b/modules/agent/src/main/java/org/dromara/jpom/common/AgentConst.java @@ -30,7 +30,7 @@ package org.dromara.jpom.common; */ public class AgentConst { /** - * 白名单文件 + * 授权文件 */ public static final String WHITELIST_DIRECTORY = "whitelistDirectory.json"; /** diff --git a/modules/agent/src/main/java/org/dromara/jpom/common/AgentExceptionHandler.java b/modules/agent/src/main/java/org/dromara/jpom/common/AgentExceptionHandler.java index e88a47f5e..63034f91b 100644 --- a/modules/agent/src/main/java/org/dromara/jpom/common/AgentExceptionHandler.java +++ b/modules/agent/src/main/java/org/dromara/jpom/common/AgentExceptionHandler.java @@ -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; /** diff --git a/modules/agent/src/main/java/org/dromara/jpom/common/BaseAgentController.java b/modules/agent/src/main/java/org/dromara/jpom/common/BaseAgentController.java index 798ee2f80..e665eef8d 100644 --- a/modules/agent/src/main/java/org/dromara/jpom/common/BaseAgentController.java +++ b/modules/agent/src/main/java/org/dromara/jpom/common/BaseAgentController.java @@ -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; } } diff --git a/modules/agent/src/main/java/org/dromara/jpom/common/WebConfigurer.java b/modules/agent/src/main/java/org/dromara/jpom/common/WebConfigurer.java index 14c4b4081..bdb3eb104 100644 --- a/modules/agent/src/main/java/org/dromara/jpom/common/WebConfigurer.java +++ b/modules/agent/src/main/java/org/dromara/jpom/common/WebConfigurer.java @@ -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 diff --git a/modules/agent/src/main/java/org/dromara/jpom/common/commander/AbstractProjectCommander.java b/modules/agent/src/main/java/org/dromara/jpom/common/commander/AbstractProjectCommander.java index 12c84bd01..36bb26cb6 100644 --- a/modules/agent/src/main/java/org/dromara/jpom/common/commander/AbstractProjectCommander.java +++ b/modules/agent/src/main/java/org/dromara/jpom/common/commander/AbstractProjectCommander.java @@ -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> 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 listNetstat(int pid, boolean listening); + protected abstract List 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 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 fileList = NodeProjectInfoModel.listJars(nodeProjectInfoModel); + } else if (checkRunMode == RunMode.Jar || checkRunMode == RunMode.JarWar) { + + List 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 status = this.runDsl(nodeProjectInfoModel, "status", (baseProcess, action) -> { + List 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 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 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 list = listNetstat(pid, true); + List 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 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 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 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(); + } } diff --git a/modules/agent/src/main/java/org/dromara/jpom/common/commander/AbstractSystemCommander.java b/modules/agent/src/main/java/org/dromara/jpom/common/commander/AbstractSystemCommander.java index 618e25d0b..b2706c233 100644 --- a/modules/agent/src/main/java/org/dromara/jpom/common/commander/AbstractSystemCommander.java +++ b/modules/agent/src/main/java/org/dromara/jpom/common/commander/AbstractSystemCommander.java @@ -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 命令 diff --git a/modules/agent/src/main/java/org/dromara/jpom/common/commander/BaseUnixProjectCommander.java b/modules/agent/src/main/java/org/dromara/jpom/common/commander/BaseUnixProjectCommander.java index 618e7e173..48908c914 100644 --- a/modules/agent/src/main/java/org/dromara/jpom/common/commander/BaseUnixProjectCommander.java +++ b/modules/agent/src/main/java/org/dromara/jpom/common/commander/BaseUnixProjectCommander.java @@ -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 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 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; + } } diff --git a/modules/agent/src/main/java/org/dromara/jpom/common/commander/CommandOpResult.java b/modules/agent/src/main/java/org/dromara/jpom/common/commander/CommandOpResult.java index c06d055c6..b089c14f7 100644 --- a/modules/agent/src/main/java/org/dromara/jpom/common/commander/CommandOpResult.java +++ b/modules/agent/src/main/java/org/dromara/jpom/common/commander/CommandOpResult.java @@ -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 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) 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 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); } diff --git a/modules/agent/src/main/java/org/dromara/jpom/common/commander/Commander.java b/modules/agent/src/main/java/org/dromara/jpom/common/commander/Commander.java new file mode 100644 index 000000000..5f67def1b --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/common/commander/Commander.java @@ -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(); + } + } +} diff --git a/modules/agent/src/main/java/org/dromara/jpom/common/commander/ProjectCommander.java b/modules/agent/src/main/java/org/dromara/jpom/common/commander/ProjectCommander.java new file mode 100644 index 000000000..e682e3473 --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/common/commander/ProjectCommander.java @@ -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); +} diff --git a/modules/common/src/main/java/org/dromara/jpom/system/BaseExtConfig.java b/modules/agent/src/main/java/org/dromara/jpom/common/commander/SystemCommander.java similarity index 68% rename from modules/common/src/main/java/org/dromara/jpom/system/BaseExtConfig.java rename to modules/agent/src/main/java/org/dromara/jpom/common/commander/SystemCommander.java index 182015e47..dbb13ecac 100644 --- a/modules/common/src/main/java/org/dromara/jpom/system/BaseExtConfig.java +++ b/modules/agent/src/main/java/org/dromara/jpom/common/commander/SystemCommander.java @@ -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); } diff --git a/modules/agent/src/main/java/org/dromara/jpom/common/commander/impl/LinuxProjectCommander.java b/modules/agent/src/main/java/org/dromara/jpom/common/commander/impl/LinuxProjectCommander.java index 121805bb0..d81aab97b 100644 --- a/modules/agent/src/main/java/org/dromara/jpom/common/commander/impl/LinuxProjectCommander.java +++ b/modules/agent/src/main/java/org/dromara/jpom/common/commander/impl/LinuxProjectCommander.java @@ -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 diff --git a/modules/agent/src/main/java/org/dromara/jpom/common/commander/impl/LinuxSystemCommander.java b/modules/agent/src/main/java/org/dromara/jpom/common/commander/impl/LinuxSystemCommander.java index 5e74d431d..33c80f818 100644 --- a/modules/agent/src/main/java/org/dromara/jpom/common/commander/impl/LinuxSystemCommander.java +++ b/modules/agent/src/main/java/org/dromara/jpom/common/commander/impl/LinuxSystemCommander.java @@ -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 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 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) { diff --git a/modules/agent/src/main/java/org/dromara/jpom/common/commander/impl/MacOsProjectCommander.java b/modules/agent/src/main/java/org/dromara/jpom/common/commander/impl/MacOsProjectCommander.java index 76204b23f..ce075c9b3 100644 --- a/modules/agent/src/main/java/org/dromara/jpom/common/commander/impl/MacOsProjectCommander.java +++ b/modules/agent/src/main/java/org/dromara/jpom/common/commander/impl/MacOsProjectCommander.java @@ -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 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 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()); } } diff --git a/modules/agent/src/main/java/org/dromara/jpom/common/commander/impl/MacOsSystemCommander.java b/modules/agent/src/main/java/org/dromara/jpom/common/commander/impl/MacOsSystemCommander.java index 4515920c5..e0b02d002 100644 --- a/modules/agent/src/main/java/org/dromara/jpom/common/commander/impl/MacOsSystemCommander.java +++ b/modules/agent/src/main/java/org/dromara/jpom/common/commander/impl/MacOsSystemCommander.java @@ -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 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 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) { diff --git a/modules/agent/src/main/java/org/dromara/jpom/common/commander/impl/WindowsProjectCommander.java b/modules/agent/src/main/java/org/dromara/jpom/common/commander/impl/WindowsProjectCommander.java index b9702fdfa..3516bb6e5 100644 --- a/modules/agent/src/main/java/org/dromara/jpom/common/commander/impl/WindowsProjectCommander.java +++ b/modules/agent/src/main/java/org/dromara/jpom/common/commander/impl/WindowsProjectCommander.java @@ -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 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 } diff --git a/modules/agent/src/main/java/org/dromara/jpom/common/commander/impl/WindowsSystemCommander.java b/modules/agent/src/main/java/org/dromara/jpom/common/commander/impl/WindowsSystemCommander.java index 0d264a659..08565708c 100644 --- a/modules/agent/src/main/java/org/dromara/jpom/common/commander/impl/WindowsSystemCommander.java +++ b/modules/agent/src/main/java/org/dromara/jpom/common/commander/impl/WindowsSystemCommander.java @@ -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) { diff --git a/modules/agent/src/main/java/org/dromara/jpom/common/interceptor/AuthorizeInterceptor.java b/modules/agent/src/main/java/org/dromara/jpom/common/interceptor/AuthorizeInterceptor.java index 5e2b262f8..98e146212 100644 --- a/modules/agent/src/main/java/org/dromara/jpom/common/interceptor/AuthorizeInterceptor.java +++ b/modules/agent/src/main/java/org/dromara/jpom/common/interceptor/AuthorizeInterceptor.java @@ -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 diff --git a/modules/agent/src/main/java/org/dromara/jpom/system/AgentAuthorize.java b/modules/agent/src/main/java/org/dromara/jpom/configuration/AgentAuthorize.java similarity index 83% rename from modules/agent/src/main/java/org/dromara/jpom/system/AgentAuthorize.java rename to modules/agent/src/main/java/org/dromara/jpom/configuration/AgentAuthorize.java index 2a1d0dafb..5920992e8 100644 --- a/modules/agent/src/main/java/org/dromara/jpom/system/AgentAuthorize.java +++ b/modules/agent/src/main/java/org/dromara/jpom/configuration/AgentAuthorize.java @@ -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; - } } diff --git a/modules/agent/src/main/java/org/dromara/jpom/system/AgentConfig.java b/modules/agent/src/main/java/org/dromara/jpom/configuration/AgentConfig.java similarity index 50% rename from modules/agent/src/main/java/org/dromara/jpom/system/AgentConfig.java rename to modules/agent/src/main/java/org/dromara/jpom/configuration/AgentConfig.java index 60a155978..1687c4dc5 100644 --- a/modules/agent/src/main/java/org/dromara/jpom/system/AgentConfig.java +++ b/modules/agent/src/main/java/org/dromara/jpom/configuration/AgentConfig.java @@ -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); } } diff --git a/modules/agent/src/main/java/org/dromara/jpom/configuration/ProjectConfig.java b/modules/agent/src/main/java/org/dromara/jpom/configuration/ProjectConfig.java new file mode 100644 index 000000000..c197ea5c6 --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/configuration/ProjectConfig.java @@ -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; + }); + } +} diff --git a/modules/agent/src/main/java/org/dromara/jpom/configuration/ProjectLogConfig.java b/modules/agent/src/main/java/org/dromara/jpom/configuration/ProjectLogConfig.java new file mode 100644 index 000000000..3a7cfa02e --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/configuration/ProjectLogConfig.java @@ -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); + } +} diff --git a/modules/agent/src/main/java/org/dromara/jpom/configuration/SystemConfig.java b/modules/agent/src/main/java/org/dromara/jpom/configuration/SystemConfig.java new file mode 100644 index 000000000..7841de7b8 --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/configuration/SystemConfig.java @@ -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 { +} diff --git a/modules/agent/src/main/java/org/dromara/jpom/controller/IndexController.java b/modules/agent/src/main/java/org/dromara/jpom/controller/IndexController.java index 5ed325174..f3e87afce 100644 --- a/modules/agent/src/main/java/org/dromara/jpom/controller/IndexController.java +++ b/modules/agent/src/main/java/org/dromara/jpom/controller/IndexController.java @@ -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 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 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"; } diff --git a/modules/agent/src/main/java/org/dromara/jpom/controller/manage/FileManageController.java b/modules/agent/src/main/java/org/dromara/jpom/controller/manage/FileManageController.java index 40864f33a..241cb9810 100644 --- a/modules/agent/src/main/java/org/dromara/jpom/controller/manage/FileManageController.java +++ b/modules/agent/src/main/java/org/dromara/jpom/controller/manage/FileManageController.java @@ -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; diff --git a/modules/agent/src/main/java/org/dromara/jpom/controller/manage/ManageEditProjectController.java b/modules/agent/src/main/java/org/dromara/jpom/controller/manage/ManageEditProjectController.java index 8f9ec6852..9d65cb1a1 100644 --- a/modules/agent/src/main/java/org/dromara/jpom/controller/manage/ManageEditProjectController.java +++ b/modules/agent/src/main/java/org/dromara/jpom/controller/manage/ManageEditProjectController.java @@ -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 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 save(NodeProjectInfoModel projectInfo, NodeProjectInfoModel exits, boolean previewData) { - projectInfo.setWorkspaceId(getWorkspaceId()); - + private IJsonMessage 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 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 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 deleteProject(String thorough) { + public IJsonMessage 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 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 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("修改成功"); } diff --git a/modules/agent/src/main/java/org/dromara/jpom/controller/manage/ProjectFileBackupController.java b/modules/agent/src/main/java/org/dromara/jpom/controller/manage/ProjectFileBackupController.java index 25c014dc7..0753633ba 100644 --- a/modules/agent/src/main/java/org/dromara/jpom/controller/manage/ProjectFileBackupController.java +++ b/modules/agent/src/main/java/org/dromara/jpom/controller/manage/ProjectFileBackupController.java @@ -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 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> 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 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 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; diff --git a/modules/agent/src/main/java/org/dromara/jpom/controller/manage/ProjectFileControl.java b/modules/agent/src/main/java/org/dromara/jpom/controller/manage/ProjectFileControl.java index 24633623f..e67e0feeb 100644 --- a/modules/agent/src/main/java/org/dromara/jpom/controller/manage/ProjectFileControl.java +++ b/modules/agent/src/main/java/org/dromara/jpom/controller/manage/ProjectFileControl.java @@ -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> 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 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 files = FileUtil.loopFiles(path); // 将所有的文件信息组装并签名 List 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 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 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 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 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 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 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 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("操作成功"); } diff --git a/modules/agent/src/main/java/org/dromara/jpom/controller/manage/ProjectListController.java b/modules/agent/src/main/java/org/dromara/jpom/controller/manage/ProjectListController.java index 06e96f7fe..2dde1876c 100644 --- a/modules/agent/src/main/java/org/dromara/jpom/controller/manage/ProjectListController.java +++ b/modules/agent/src/main/java/org/dromara/jpom/controller/manage/ProjectListController.java @@ -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 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> getProjectInfo() { - try { - // 查询数据 - List nodeProjectInfoModels = projectInfoService.list(); - return JsonMessage.success("查询成功!", nodeProjectInfoModels); - } catch (Exception e) { - log.error(e.getMessage(), e); - return new JsonMessage<>(500, "查询异常:" + e.getMessage()); - } + // 查询数据 + List nodeProjectInfoModels = projectInfoService.list(); + return JsonMessage.success("", nodeProjectInfoModels); } } diff --git a/modules/agent/src/main/java/org/dromara/jpom/controller/manage/log/LogBackController.java b/modules/agent/src/main/java/org/dromara/jpom/controller/manage/ProjectLogBackController.java similarity index 78% rename from modules/agent/src/main/java/org/dromara/jpom/controller/manage/log/LogBackController.java rename to modules/agent/src/main/java/org/dromara/jpom/controller/manage/ProjectLogBackController.java index a810ffcd1..77b098df7 100644 --- a/modules/agent/src/main/java/org/dromara/jpom/controller/manage/log/LogBackController.java +++ b/modules/agent/src/main/java/org/dromara/jpom/controller/manage/ProjectLogBackController.java @@ -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 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 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 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; diff --git a/modules/agent/src/main/java/org/dromara/jpom/controller/manage/ProjectStatusController.java b/modules/agent/src/main/java/org/dromara/jpom/controller/manage/ProjectStatusController.java index a0d0e009f..dfddb65bd 100644 --- a/modules/agent/src/main/java/org/dromara/jpom/controller/manage/ProjectStatusController.java +++ b/modules/agent/src/main/java/org/dromara/jpom/controller/manage/ProjectStatusController.java @@ -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 restart(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "项目id 不正确") String id) { + @RequestMapping(value = "operate", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage 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 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 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); } } diff --git a/modules/agent/src/main/java/org/dromara/jpom/controller/script/ScriptController.java b/modules/agent/src/main/java/org/dromara/jpom/controller/script/ScriptController.java index 9f403ebc6..4208bd390 100644 --- a/modules/agent/src/main/java/org/dromara/jpom/controller/script/ScriptController.java +++ b/modules/agent/src/main/java/org/dromara/jpom/controller/script/ScriptController.java @@ -157,8 +157,8 @@ public class ScriptController extends BaseAgentController { */ @RequestMapping(value = "log", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) public IJsonMessage 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 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 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("修改成功"); + } } diff --git a/modules/agent/src/main/java/org/dromara/jpom/controller/system/AgentCacheManageController.java b/modules/agent/src/main/java/org/dromara/jpom/controller/system/AgentCacheManageController.java index 91f619fc1..418e73384 100644 --- a/modules/agent/src/main/java/org/dromara/jpom/controller/system/AgentCacheManageController.java +++ b/modules/agent/src/main/java/org/dromara/jpom/controller/system/AgentCacheManageController.java @@ -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); } diff --git a/modules/agent/src/main/java/org/dromara/jpom/controller/system/SystemUpdateController.java b/modules/agent/src/main/java/org/dromara/jpom/controller/system/SystemUpdateController.java index e051fe4ce..425671121 100644 --- a/modules/agent/src/main/java/org/dromara/jpom/controller/system/SystemUpdateController.java +++ b/modules/agent/src/main/java/org/dromara/jpom/controller/system/SystemUpdateController.java @@ -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; diff --git a/modules/agent/src/main/java/org/dromara/jpom/controller/system/WhitelistDirectoryController.java b/modules/agent/src/main/java/org/dromara/jpom/controller/system/WhitelistDirectoryController.java index a7ce26e1b..9ce935aeb 100644 --- a/modules/agent/src/main/java/org/dromara/jpom/controller/system/WhitelistDirectoryController.java +++ b/modules/agent/src/main/java/org/dromara/jpom/controller/system/WhitelistDirectoryController.java @@ -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 list = AgentWhitelist.parseToList(project, true, "项目路径白名单不能为空"); + List list = AgentWhitelist.parseToList(project, true, "项目路径授权不能为空"); // List allowEditSuffixList = AgentWhitelist.parseToList(allowEditSuffix, "允许编辑的文件后缀不能为空"); return save(list, allowEditSuffixList); @@ -81,9 +77,9 @@ public class WhitelistDirectoryController extends BaseJpomController { List allowEditSuffixList) { List 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 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 jsonArray) { + return AgentWhitelist.findStartsWith(jsonArray); } } diff --git a/modules/agent/src/main/java/org/dromara/jpom/model/data/DslYmlDto.java b/modules/agent/src/main/java/org/dromara/jpom/model/data/DslYmlDto.java index e036f69ab..ed57a4d16 100644 --- a/modules/agent/src/main/java/org/dromara/jpom/model/data/DslYmlDto.java +++ b/modules/agent/src/main/java/org/dromara/jpom/model/data/DslYmlDto.java @@ -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 scriptEnv; - - /** - * 通过 脚本模版运行 - * - * @return true - */ - public boolean runByScript() { - return StrUtil.isNotEmpty(this.getScriptId()); - } } @Data diff --git a/modules/agent/src/main/java/org/dromara/jpom/model/data/NodeProjectInfoModel.java b/modules/agent/src/main/java/org/dromara/jpom/model/data/NodeProjectInfoModel.java index fa16b0810..f2a0612eb 100644 --- a/modules/agent/src/main/java/org/dromara/jpom/model/data/NodeProjectInfoModel.java +++ b/modules/agent/src/main/java/org/dromara/jpom/model/data/NodeProjectInfoModel.java @@ -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 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 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 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; } } diff --git a/modules/agent/src/main/java/org/dromara/jpom/model/system/NetstatModel.java b/modules/agent/src/main/java/org/dromara/jpom/model/system/NetstatModel.java index 145efd094..de058ab85 100644 --- a/modules/agent/src/main/java/org/dromara/jpom/model/system/NetstatModel.java +++ b/modules/agent/src/main/java/org/dromara/jpom/model/system/NetstatModel.java @@ -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; } diff --git a/modules/agent/src/main/java/org/dromara/jpom/script/DslScriptBuilder.java b/modules/agent/src/main/java/org/dromara/jpom/script/DslScriptBuilder.java index 86197566a..8707a6c41 100644 --- a/modules/agent/src/main/java/org/dromara/jpom/script/DslScriptBuilder.java +++ b/modules/agent/src/main/java/org/dromara/jpom/script/DslScriptBuilder.java @@ -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 { /** * 执行 + *

+ * 0 退出码 + * 1 日志 */ - public List syncExecute() { + public Tuple syncExecute() { ProcessBuilder processBuilder = this.init(); List 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 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 map1 = MapUtil.newHashMap(); - for (Map.Entry 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; - } } diff --git a/modules/agent/src/main/java/org/dromara/jpom/service/BaseDataService.java b/modules/agent/src/main/java/org/dromara/jpom/service/BaseDataService.java deleted file mode 100644 index 7d9236c3d..000000000 --- a/modules/agent/src/main/java/org/dromara/jpom/service/BaseDataService.java +++ /dev/null @@ -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 getJsonObjectById(String file, String id, Class 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); - } -} diff --git a/modules/agent/src/main/java/org/dromara/jpom/service/BaseOperService.java b/modules/agent/src/main/java/org/dromara/jpom/service/BaseOperService.java index 110be9324..72615b024 100644 --- a/modules/agent/src/main/java/org/dromara/jpom/service/BaseOperService.java +++ b/modules/agent/src/main/java/org/dromara/jpom/service/BaseOperService.java @@ -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 extends BaseDataService { +public abstract class BaseOperService { private final String fileName; - private final Class typeArgument; + private final Class typeArgument; + private final Lock lock = LockUtil.createStampLock().asWriteLock(); public BaseOperService(String fileName) { this.fileName = fileName; - this.typeArgument = ClassUtil.getTypeArgument(this.getClass()); + this.typeArgument = (Class) ClassUtil.getTypeArgument(this.getClass()); } /** @@ -54,7 +67,12 @@ public abstract class BaseOperService extends BaseDataServi * @return list */ public List list() { - return (List) list(typeArgument); + return list(typeArgument); + } + + public int size() { + List list = this.list(); + return CollUtil.size(list); } public List list(Class cls) { @@ -79,7 +97,7 @@ public abstract class BaseOperService 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 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 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 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 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); + } } diff --git a/modules/agent/src/main/java/org/dromara/jpom/service/BaseWorkspaceOptService.java b/modules/agent/src/main/java/org/dromara/jpom/service/BaseWorkspaceOptService.java index c9f7b1bd3..94175632d 100644 --- a/modules/agent/src/main/java/org/dromara/jpom/service/BaseWorkspaceOptService.java +++ b/modules/agent/src/main/java/org/dromara/jpom/service/BaseWorkspaceOptService.java @@ -61,4 +61,10 @@ public abstract class BaseWorkspaceOptService exte } super.updateItem(data); } + + @Override + public void updateById(T updateData, String id) { + updateData.setModifyTime(DateUtil.now()); + super.updateById(updateData, id); + } } diff --git a/modules/agent/src/main/java/org/dromara/jpom/script/ProjectFileBackupUtil.java b/modules/agent/src/main/java/org/dromara/jpom/service/ProjectFileBackupService.java similarity index 74% rename from modules/agent/src/main/java/org/dromara/jpom/script/ProjectFileBackupUtil.java rename to modules/agent/src/main/java/org/dromara/jpom/service/ProjectFileBackupService.java index 41fa952b4..f8c34caff 100644 --- a/modules/agent/src/main/java/org/dromara/jpom/script/ProjectFileBackupUtil.java +++ b/modules/agent/src/main/java/org/dromara/jpom/service/ProjectFileBackupService.java @@ -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 backupFiles = ProjectFileBackupUtil.listFiles(backupItemPath.getAbsolutePath()); - Map nowFiles = ProjectFileBackupUtil.listFiles(projectPath); + Map backupFiles = this.listFiles(backupItemPath); + Map 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 listFiles(String path) { + private Map listFiles(File path) { // 将所有的文件信息组装并签名 List files = FileUtil.loopFiles(path); List collect = files.stream().map(file -> { diff --git a/modules/agent/src/main/java/org/dromara/jpom/service/WhitelistDirectoryService.java b/modules/agent/src/main/java/org/dromara/jpom/service/WhitelistDirectoryService.java index 747082239..a44ce559c 100644 --- a/modules/agent/src/main/java/org/dromara/jpom/service/WhitelistDirectoryService.java +++ b/modules/agent/src/main/java/org/dromara/jpom/service/WhitelistDirectoryService.java @@ -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 { + + 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 list = CollUtil.newArrayList(item); - List checkOk = AgentWhitelist.covertToArray(list, "项目路径白名单不能位于Jpom目录下"); + List checkOk = AgentWhitelist.covertToArray(list, "项目路径授权不能位于Jpom目录下"); AgentWhitelist agentWhitelist = getWhitelist(); List project = agentWhitelist.getProject(); @@ -85,13 +89,13 @@ public class WhitelistDirectoryService extends BaseDataService { public boolean checkProjectDirectory(String path) { AgentWhitelist agentWhitelist = getWhitelist(); - List list = agentWhitelist.project(); + List list = agentWhitelist.getProject(); return AgentWhitelist.checkPath(list, path); } /** - * 保存白名单 + * 保存授权 * * @param jsonObject 实体 */ diff --git a/modules/agent/src/main/java/org/dromara/jpom/service/manage/ConsoleService.java b/modules/agent/src/main/java/org/dromara/jpom/service/manage/ConsoleService.java deleted file mode 100644 index 67a60dcf3..000000000 --- a/modules/agent/src/main/java/org/dromara/jpom/service/manage/ConsoleService.java +++ /dev/null @@ -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; - } -} diff --git a/modules/agent/src/main/java/org/dromara/jpom/service/manage/ProjectInfoService.java b/modules/agent/src/main/java/org/dromara/jpom/service/manage/ProjectInfoService.java index 8364ec524..1b80e9749 100644 --- a/modules/agent/src/main/java/org/dromara/jpom/service/manage/ProjectInfoService.java +++ b/modules/agent/src/main/java/org/dromara/jpom/service/manage/ProjectInfoService.java @@ -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 { - public ProjectInfoService() { super(AgentConst.PROJECT); } -// public HashSet getAllGroup() { -// //获取所有分组 -// List nodeProjectInfoModels = list(); -// HashSet 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"); + } + } diff --git a/modules/agent/src/main/java/org/dromara/jpom/service/script/DslScriptServer.java b/modules/agent/src/main/java/org/dromara/jpom/service/script/DslScriptServer.java new file mode 100644 index 000000000..41b74ef1e --- /dev/null +++ b/modules/agent/src/main/java/org/dromara/jpom/service/script/DslScriptServer.java @@ -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 map1 = MapUtil.newHashMap(); + for (Map.Entry 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; + } +} diff --git a/modules/agent/src/main/java/org/dromara/jpom/socket/AgentFreeWebSocketScriptHandle.java b/modules/agent/src/main/java/org/dromara/jpom/socket/AgentFreeWebSocketScriptHandle.java index 424f60f52..fa21ee03e 100644 --- a/modules/agent/src/main/java/org/dromara/jpom/socket/AgentFreeWebSocketScriptHandle.java +++ b/modules/agent/src/main/java/org/dromara/jpom/socket/AgentFreeWebSocketScriptHandle.java @@ -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 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) { diff --git a/modules/agent/src/main/java/org/dromara/jpom/socket/AgentWebSocketConsoleHandle.java b/modules/agent/src/main/java/org/dromara/jpom/socket/AgentWebSocketConsoleHandle.java index 71e490ad8..f6d165636 100644 --- a/modules/agent/src/main/java/org/dromara/jpom/socket/AgentWebSocketConsoleHandle.java +++ b/modules/agent/src/main/java/org/dromara/jpom/socket/AgentWebSocketConsoleHandle.java @@ -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 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); } diff --git a/modules/agent/src/main/java/org/dromara/jpom/socket/AgentWebSocketScriptHandle.java b/modules/agent/src/main/java/org/dromara/jpom/socket/AgentWebSocketScriptHandle.java index 98e0c8754..9a6f0d211 100644 --- a/modules/agent/src/main/java/org/dromara/jpom/socket/AgentWebSocketScriptHandle.java +++ b/modules/agent/src/main/java/org/dromara/jpom/socket/AgentWebSocketScriptHandle.java @@ -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); } diff --git a/modules/agent/src/main/java/org/dromara/jpom/socket/AgentWebSocketSystemLogHandle.java b/modules/agent/src/main/java/org/dromara/jpom/socket/AgentWebSocketSystemLogHandle.java index edf5bc032..d70615a35 100644 --- a/modules/agent/src/main/java/org/dromara/jpom/socket/AgentWebSocketSystemLogHandle.java +++ b/modules/agent/src/main/java/org/dromara/jpom/socket/AgentWebSocketSystemLogHandle.java @@ -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 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 diff --git a/modules/agent/src/main/java/org/dromara/jpom/socket/AgentWebSocketUpdateHandle.java b/modules/agent/src/main/java/org/dromara/jpom/socket/AgentWebSocketUpdateHandle.java index 08786933c..3886b7853 100644 --- a/modules/agent/src/main/java/org/dromara/jpom/socket/AgentWebSocketUpdateHandle.java +++ b/modules/agent/src/main/java/org/dromara/jpom/socket/AgentWebSocketUpdateHandle.java @@ -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()); } diff --git a/modules/agent/src/main/java/org/dromara/jpom/socket/BaseAgentWebSocketHandle.java b/modules/agent/src/main/java/org/dromara/jpom/socket/BaseAgentWebSocketHandle.java index 09c77549e..dfb6432ab 100644 --- a/modules/agent/src/main/java/org/dromara/jpom/socket/BaseAgentWebSocketHandle.java +++ b/modules/agent/src/main/java/org/dromara/jpom/socket/BaseAgentWebSocketHandle.java @@ -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 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> 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); diff --git a/modules/agent/src/main/java/org/dromara/jpom/system/AgentStartInit.java b/modules/agent/src/main/java/org/dromara/jpom/system/AgentStartInit.java index 724078fda..f6fa2575e 100644 --- a/modules/agent/src/main/java/org/dromara/jpom/system/AgentStartInit.java +++ b/modules/agent/src/main/java/org/dromara/jpom/system/AgentStartInit.java @@ -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 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 list = projectInfoService.list(); - if (CollUtil.isEmpty(list)) { + List allProject = projectInfoService.list(); + if (CollUtil.isEmpty(allProject)) { return; } - list = list.stream().filter(nodeProjectInfoModel -> nodeProjectInfoModel.getAutoStart() != null && nodeProjectInfoModel.getAutoStart()).collect(Collectors.toList()); - List finalList = list; + List 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); + } + } + }); } diff --git a/modules/agent/src/main/resources/application.yml b/modules/agent/src/main/resources/application.yml index af78f7663..81cf82e5f 100644 --- a/modules/agent/src/main/resources/application.yml +++ b/modules/agent/src/main/resources/application.yml @@ -6,9 +6,6 @@ jpom: agent-name: jpomAgent # agent 端管理密码,非必填,如果为空Jpom 会自动生成一串随机字符串当密码 agent-pwd: - whitelist: - # 白名单目录是否验证包含关系 - check-starts-with: true project: # 停止、启动项目(项目状态检测)等待的时长 单位秒 status-wait-time: 10 diff --git a/modules/agent/src/main/resources/config_default/application.yml b/modules/agent/src/main/resources/config_default/application.yml index 1ad36e1e2..8cec577a8 100644 --- a/modules/agent/src/main/resources/config_default/application.yml +++ b/modules/agent/src/main/resources/config_default/application.yml @@ -6,9 +6,6 @@ jpom: agent-name: jpomAgent # agent 端管理密码,非必填,如果为空Jpom 会自动生成一串随机字符串当密码 agent-pwd: - whitelist: - # 白名单目录是否验证包含关系 - check-starts-with: true project: # 停止、启动项目(项目状态检测)等待的时长 单位秒 status-wait-time: 10 diff --git a/modules/common/pom.xml b/modules/common/pom.xml index f7fd5f690..3298d6595 100644 --- a/modules/common/pom.xml +++ b/modules/common/pom.xml @@ -29,13 +29,13 @@ jpom-parent org.dromara.jpom - 2.10.47 + 2.11.0.8 ../../pom.xml 4.0.0 Jpom Common common - 2.10.47 + 2.11.0.8 @@ -79,7 +79,6 @@ hutool-cron - org.springframework.boot diff --git a/modules/common/src/main/java/org/dromara/jpom/JpomApplication.java b/modules/common/src/main/java/org/dromara/jpom/JpomApplication.java index a1628ebce..7c5f59d26 100644 --- a/modules/common/src/main/java/org/dromara/jpom/JpomApplication.java +++ b/modules/common/src/main/java/org/dromara/jpom/JpomApplication.java @@ -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) { diff --git a/modules/common/src/main/java/org/dromara/jpom/common/BaseJpomController.java b/modules/common/src/main/java/org/dromara/jpom/common/BaseJpomController.java index 4bf4816bc..3dfec23f3 100644 --- a/modules/common/src/main/java/org/dromara/jpom/common/BaseJpomController.java +++ b/modules/common/src/main/java/org/dromara/jpom/common/BaseJpomController.java @@ -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 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); } diff --git a/modules/common/src/main/java/org/dromara/jpom/common/JpomManifest.java b/modules/common/src/main/java/org/dromara/jpom/common/JpomManifest.java index f46ac2174..0eedb0f12 100644 --- a/modules/common/src/main/java/org/dromara/jpom/common/JpomManifest.java +++ b/modules/common/src/main/java/org/dromara/jpom/common/JpomManifest.java @@ -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) diff --git a/modules/common/src/main/java/org/dromara/jpom/common/ServerOpenApi.java b/modules/common/src/main/java/org/dromara/jpom/common/ServerOpenApi.java index 9d50d7c8e..0a9be812c 100644 --- a/modules/common/src/main/java/org/dromara/jpom/common/ServerOpenApi.java +++ b/modules/common/src/main/java/org/dromara/jpom/common/ServerOpenApi.java @@ -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}"; /** * 获取当前构建状态 */ diff --git a/modules/common/src/main/java/org/dromara/jpom/common/multipart/MultipartFileBuilder.java b/modules/common/src/main/java/org/dromara/jpom/common/multipart/MultipartFileBuilder.java deleted file mode 100644 index f6b828d52..000000000 --- a/modules/common/src/main/java/org/dromara/jpom/common/multipart/MultipartFileBuilder.java +++ /dev/null @@ -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 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 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 list = saveAndNames(); - return list.get(0); - } - - /** - * 上传文件,并且返回原文件名 - * - * @return 集合 - * @throws IOException IO - */ - public List saveAndNames() throws IOException { - if (fieldNames.isEmpty()) { - throw new IllegalArgumentException("fieldNames:empty"); - } - List list = new ArrayList<>(); - for (String fieldName : fieldNames) { - if (this.multiple) { - List 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; - } -} diff --git a/modules/common/src/main/java/org/dromara/jpom/common/BaseExceptionHandler.java b/modules/common/src/main/java/org/dromara/jpom/exception/BaseExceptionHandler.java similarity index 99% rename from modules/common/src/main/java/org/dromara/jpom/common/BaseExceptionHandler.java rename to modules/common/src/main/java/org/dromara/jpom/exception/BaseExceptionHandler.java index dfe7f2623..9c8786564 100644 --- a/modules/common/src/main/java/org/dromara/jpom/common/BaseExceptionHandler.java +++ b/modules/common/src/main/java/org/dromara/jpom/exception/BaseExceptionHandler.java @@ -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; diff --git a/modules/common/src/main/java/org/dromara/jpom/common/IllegalArgument2Exception.java b/modules/common/src/main/java/org/dromara/jpom/exception/IllegalArgument2Exception.java similarity index 97% rename from modules/common/src/main/java/org/dromara/jpom/common/IllegalArgument2Exception.java rename to modules/common/src/main/java/org/dromara/jpom/exception/IllegalArgument2Exception.java index d2c5af606..84be270d3 100644 --- a/modules/common/src/main/java/org/dromara/jpom/common/IllegalArgument2Exception.java +++ b/modules/common/src/main/java/org/dromara/jpom/exception/IllegalArgument2Exception.java @@ -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 diff --git a/modules/common/src/main/java/org/dromara/jpom/exception/LogRecorderCloseException.java b/modules/common/src/main/java/org/dromara/jpom/exception/LogRecorderCloseException.java new file mode 100644 index 000000000..fbc74acc4 --- /dev/null +++ b/modules/common/src/main/java/org/dromara/jpom/exception/LogRecorderCloseException.java @@ -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("日志记录器被关闭/或者未启用"); + } +} diff --git a/modules/common/src/main/java/org/dromara/jpom/model/EnvironmentMapBuilder.java b/modules/common/src/main/java/org/dromara/jpom/model/EnvironmentMapBuilder.java index ce6e8d9ef..b3330c963 100644 --- a/modules/common/src/main/java/org/dromara/jpom/model/EnvironmentMapBuilder.java +++ b/modules/common/src/main/java/org/dromara/jpom/model/EnvironmentMapBuilder.java @@ -90,6 +90,10 @@ public class EnvironmentMapBuilder { Map map = new LinkedHashMap<>(this.map.size()); for (Map.Entry 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 -> { diff --git a/modules/common/src/main/java/org/dromara/jpom/model/RunMode.java b/modules/common/src/main/java/org/dromara/jpom/model/RunMode.java index cfe472ae8..23b8e71af 100644 --- a/modules/common/src/main/java/org/dromara/jpom/model/RunMode.java +++ b/modules/common/src/main/java/org/dromara/jpom/model/RunMode.java @@ -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, } diff --git a/modules/common/src/main/java/org/dromara/jpom/model/data/AgentWhitelist.java b/modules/common/src/main/java/org/dromara/jpom/model/data/AgentWhitelist.java index a96611815..94c5015e9 100644 --- a/modules/common/src/main/java/org/dromara/jpom/model/data/AgentWhitelist.java +++ b/modules/common/src/main/java/org/dromara/jpom/model/data/AgentWhitelist.java @@ -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 project; /** @@ -64,23 +63,6 @@ public class AgentWhitelist extends BaseJsonModel { */ private List allowEditSuffix; - public static String convertRealPath(String path) { - String val = String.format("/%s/", path); - return FileUtil.normalize(val); - } - - public static List useConvert(List list) { - if (list == null) { - return null; - } - return list.stream().map(AgentWhitelist::convertRealPath).collect(Collectors.toList()); - } - - - public List project() { - return useConvert(project); - } - /** * 格式化,判断是否与jpom 数据路径冲突 * @@ -88,16 +70,32 @@ public class AgentWhitelist extends BaseJsonModel { * @return null 是有冲突的 */ public static List covertToArray(List list, String errorMsg) { + return covertToArray(list, -1, errorMsg); + } + + /** + * 格式化,判断是否与jpom 数据路径冲突 + * + * @param list list + * @return null 是有冲突的 + */ + public static List covertToArray(List 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 jsonArray) { + return findStartsWith(jsonArray, 0); + } + + /** + * 检查授权包含关系 + * + * @param jsonArray 要检查的对象 + * @param start 检查的坐标 + * @return null 正常 + */ + private static String findStartsWith(List 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; + } } diff --git a/modules/common/src/main/java/org/dromara/jpom/script/BaseRunScript.java b/modules/common/src/main/java/org/dromara/jpom/script/BaseRunScript.java index a931ffdd3..dd76fdf42 100644 --- a/modules/common/src/main/java/org/dromara/jpom/script/BaseRunScript.java +++ b/modules/common/src/main/java/org/dromara/jpom/script/BaseRunScript.java @@ -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); } /** diff --git a/modules/common/src/main/java/org/dromara/jpom/socket/ConsoleCommandOp.java b/modules/common/src/main/java/org/dromara/jpom/socket/ConsoleCommandOp.java index 7c50d4a43..acb5d7351 100644 --- a/modules/common/src/main/java/org/dromara/jpom/socket/ConsoleCommandOp.java +++ b/modules/common/src/main/java/org/dromara/jpom/socket/ConsoleCommandOp.java @@ -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; + } } diff --git a/modules/common/src/main/java/org/dromara/jpom/system/ExtConfigBean.java b/modules/common/src/main/java/org/dromara/jpom/system/ExtConfigBean.java index 56a51d945..78841fdc2 100644 --- a/modules/common/src/main/java/org/dromara/jpom/system/ExtConfigBean.java +++ b/modules/common/src/main/java/org/dromara/jpom/system/ExtConfigBean.java @@ -58,10 +58,8 @@ import java.util.function.Function; */ @Slf4j public class ExtConfigBean { - - /** - * + * 控制台日志编码 */ private static Charset consoleLogCharset; diff --git a/modules/common/src/main/java/org/dromara/jpom/util/CommandUtil.java b/modules/common/src/main/java/org/dromara/jpom/util/CommandUtil.java index 94ce9854a..df6c41c45 100644 --- a/modules/common/src/main/java/org/dromara/jpom/util/CommandUtil.java +++ b/modules/common/src/main/java/org/dromara/jpom/util/CommandUtil.java @@ -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 env, String args, BiConsumer 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 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); + } } diff --git a/modules/common/src/main/java/org/dromara/jpom/util/JvmUtil.java b/modules/common/src/main/java/org/dromara/jpom/util/JvmUtil.java index 035472940..af1c5295a 100644 --- a/modules/common/src/main/java/org/dromara/jpom/util/JvmUtil.java +++ b/modules/common/src/main/java/org/dromara/jpom/util/JvmUtil.java @@ -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 list = StrSplitter.splitTrim(execSystemCommand, StrUtil.LF, true); - String pidCommandInfo = list.stream().filter(s -> { - List split = StrSplitter.splitTrim(s, StrUtil.SPACE, true); - return StrUtil.equals(pid + "", CollUtil.getFirst(split)); - }).findAny().orElse(null); + String pidCommandInfo = list.stream() + .filter(s -> { + List 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 list = StrSplitter.splitTrim(execSystemCommand, StrUtil.LF, true); - Optional any = list.stream().filter(s -> checkCommandLineIsJpom(s, tag)).map(s -> { - List 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 split = StrUtil.split(s, StrUtil.SPACE); + return CollUtil.getFirst(split); + }) + .findAny() + .map(Convert::toInt) + .orElse(null); } /** diff --git a/modules/common/src/main/java/org/dromara/jpom/util/LogRecorder.java b/modules/common/src/main/java/org/dromara/jpom/util/LogRecorder.java index 8b9bfb3a8..851d3ca93 100644 --- a/modules/common/src/main/java/org/dromara/jpom/util/LogRecorder.java +++ b/modules/common/src/main/java/org/dromara/jpom/util/LogRecorder.java @@ -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); } } diff --git a/modules/common/src/main/java/org/dromara/jpom/util/StringUtil.java b/modules/common/src/main/java/org/dromara/jpom/util/StringUtil.java index 6517c7ba6..bd5679199 100644 --- a/modules/common/src/main/java/org/dromara/jpom/util/StringUtil.java +++ b/modules/common/src/main/java/org/dromara/jpom/util/StringUtil.java @@ -256,15 +256,17 @@ public class StringUtil { return MapUtil.newHashMap(); } List collect = envStrList.stream() - .map(StrUtil::trim) - .filter(s -> !StrUtil.isEmpty(s) && !StrUtil.startWith(s, "#")) - .map(s -> { - List 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 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)); } diff --git a/modules/common/src/main/resources/banner.txt b/modules/common/src/main/resources/banner.txt index 279ae0b33..f0cb83dd0 100644 --- a/modules/common/src/main/resources/banner.txt +++ b/modules/common/src/main/resources/banner.txt @@ -7,5 +7,5 @@ | | |_| - ➜ Jpom \ (•◡•) / (v2.10.47) + ➜ Jpom \ (•◡•) / (v2.11.0.8) diff --git a/modules/server/Dockerfile b/modules/server/Dockerfile index 387544260..fd4039dc6 100644 --- a/modules/server/Dockerfile +++ b/modules/server/Dockerfile @@ -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)"; \ diff --git a/modules/server/DockerfileBeta b/modules/server/DockerfileBeta index ed6e02ff6..658b028f8 100644 --- a/modules/server/DockerfileBeta +++ b/modules/server/DockerfileBeta @@ -29,7 +29,7 @@ LABEL maintainer="bwcx-jzy " 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 diff --git a/modules/server/DockerfileBetaJdk17 b/modules/server/DockerfileBetaJdk17 index 70a1cca22..9cb92b582 100644 --- a/modules/server/DockerfileBetaJdk17 +++ b/modules/server/DockerfileBetaJdk17 @@ -29,7 +29,7 @@ LABEL maintainer="bwcx-jzy " 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 diff --git a/modules/server/pom.xml b/modules/server/pom.xml index f1fc5c727..18387a709 100644 --- a/modules/server/pom.xml +++ b/modules/server/pom.xml @@ -29,13 +29,13 @@ jpom-parent org.dromara.jpom - 2.10.47 + 2.11.0.8 ../../pom.xml 4.0.0 Jpom Server server - 2.10.47 + 2.11.0.8 org.dromara.jpom.JpomServerApplication @@ -101,6 +101,11 @@ hutool-captcha + + org.bouncycastle + bcprov-jdk18on + + org.springframework.boot spring-boot-configuration-processor diff --git a/modules/server/src/main/java/org/dromara/jpom/JpomServerApplication.java b/modules/server/src/main/java/org/dromara/jpom/JpomServerApplication.java index c8ee21201..09b7b6b71 100644 --- a/modules/server/src/main/java/org/dromara/jpom/JpomServerApplication.java +++ b/modules/server/src/main/java/org/dromara/jpom/JpomServerApplication.java @@ -53,7 +53,7 @@ public class JpomServerApplication { /** * 启动执行 *

- * --rest:ip_config 重置 IP 白名单配置 + * --rest:ip_config 重置 IP 授权配置 *

* --rest:load_init_db 重新加载数据库初始化操作 *

@@ -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); diff --git a/modules/server/src/main/java/org/dromara/jpom/build/BuildExecuteManage.java b/modules/server/src/main/java/org/dromara/jpom/build/BuildExecuteManage.java index 222c40479..6583e2371 100644 --- a/modules/server/src/main/java/org/dromara/jpom/build/BuildExecuteManage.java +++ b/modules/server/src/main/java/org/dromara/jpom/build/BuildExecuteManage.java @@ -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 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); } } diff --git a/modules/server/src/main/java/org/dromara/jpom/build/BuildExecuteService.java b/modules/server/src/main/java/org/dromara/jpom/build/BuildExecuteService.java index fde5bf2f9..2dbde8363 100644 --- a/modules/server/src/main/java/org/dromara/jpom/build/BuildExecuteService.java +++ b/modules/server/src/main/java/org/dromara/jpom/build/BuildExecuteService.java @@ -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); // diff --git a/modules/server/src/main/java/org/dromara/jpom/build/ReleaseManage.java b/modules/server/src/main/java/org/dromara/jpom/build/ReleaseManage.java index aff7b18a1..66b18aef4 100644 --- a/modules/server/src/main/java/org/dromara/jpom/build/ReleaseManage.java +++ b/modules/server/src/main/java/org/dromara/jpom/build/ReleaseManage.java @@ -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 logConsumer = s -> logRecorder.info(s); + Consumer 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 logConsumer = s -> logRecorder.append(s); + Consumer 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); } } } diff --git a/modules/server/src/main/java/org/dromara/jpom/common/BaseServerController.java b/modules/server/src/main/java/org/dromara/jpom/common/BaseServerController.java index f04dbd2bb..84c047543 100644 --- a/modules/server/src/main/java/org/dromara/jpom/common/BaseServerController.java +++ b/modules/server/src/main/java/org/dromara/jpom/common/BaseServerController.java @@ -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 泛型 + * @return data + */ + protected JsonMessage 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 * diff --git a/modules/server/src/main/java/org/dromara/jpom/common/GlobalDefaultExceptionHandler.java b/modules/server/src/main/java/org/dromara/jpom/common/GlobalDefaultExceptionHandler.java index e6de4a1ac..320e80462 100644 --- a/modules/server/src/main/java/org/dromara/jpom/common/GlobalDefaultExceptionHandler.java +++ b/modules/server/src/main/java/org/dromara/jpom/common/GlobalDefaultExceptionHandler.java @@ -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 delExceptionHandler(AuthorizeException e) { + @ExceptionHandler({AgentAuthorizeException.class}) + public IJsonMessage 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 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 doPermissionException(PermissionException e) { + return new JsonMessage<>(ServerConst.AUTHORIZE_TIME_OUT_CODE, e.getMessage()); + } } diff --git a/modules/server/src/main/java/org/dromara/jpom/common/ServerConst.java b/modules/server/src/main/java/org/dromara/jpom/common/ServerConst.java index 3e227e0f4..e92137af9 100644 --- a/modules/server/src/main/java/org/dromara/jpom/common/ServerConst.java +++ b/modules/server/src/main/java/org/dromara/jpom/common/ServerConst.java @@ -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"; } diff --git a/modules/server/src/main/java/org/dromara/jpom/common/forward/JsonMessageTransformServer.java b/modules/server/src/main/java/org/dromara/jpom/common/forward/JsonMessageTransformServer.java index de40b25e7..c12661a46 100644 --- a/modules/server/src/main/java/org/dromara/jpom/common/forward/JsonMessageTransformServer.java +++ b/modules/server/src/main/java/org/dromara/jpom/common/forward/JsonMessageTransformServer.java @@ -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; diff --git a/modules/server/src/main/java/org/dromara/jpom/common/forward/NodeForward.java b/modules/server/src/main/java/org/dromara/jpom/common/forward/NodeForward.java index 322ca21fa..ca06da270 100644 --- a/modules/server/src/main/java/org/dromara/jpom/common/forward/NodeForward.java +++ b/modules/server/src/main/java/org/dromara/jpom/common/forward/NodeForward.java @@ -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("消息转换异常"); diff --git a/modules/server/src/main/java/org/dromara/jpom/common/forward/NodeUrl.java b/modules/server/src/main/java/org/dromara/jpom/common/forward/NodeUrl.java index 7505fbcf1..227c8ffbf 100644 --- a/modules/server/src/main/java/org/dromara/jpom/common/forward/NodeUrl.java +++ b/modules/server/src/main/java/org/dromara/jpom/common/forward/NodeUrl.java @@ -91,11 +91,7 @@ public enum NodeUrl { Manage_GetProjectStatus("/manage/getProjectStatus"), - Manage_Restart("/manage/restart"), - - Manage_Start("/manage/start"), - - Manage_Stop("/manage/stop"), + Manage_Operate("/manage/operate"), Manage_GetProjectPort("/manage/getProjectPort"), @@ -145,10 +141,11 @@ public enum NodeUrl { Manage_Log_logBack("/manage/log/logBack"), - Manage_Log_export("/manage/log/export.html", true), + Manage_Log_export("/manage/log/export", true), Script_List("/script/list.json"), + Script_ChangeWorkspaceId("/script/change-workspace-id"), SCRIPT_PULL_EXEC_LOG("/script/pull_exec_log"), SCRIPT_DEL_EXEC_LOG("/script/del_exec_log"), Script_Item("/script/item.json"), diff --git a/modules/server/src/main/java/org/dromara/jpom/common/interceptor/IpInterceptor.java b/modules/server/src/main/java/org/dromara/jpom/common/interceptor/IpInterceptor.java index 3fb4f0057..a40c89c3f 100644 --- a/modules/server/src/main/java/org/dromara/jpom/common/interceptor/IpInterceptor.java +++ b/modules/server/src/main/java/org/dromara/jpom/common/interceptor/IpInterceptor.java @@ -78,7 +78,7 @@ public class IpInterceptor implements HandlerMethodInterceptor { return true; } } catch (Exception e) { - log.warn("IP白名单拦截异常,请检查配置是否正确", e); + log.warn("IP授权拦截异常,请检查配置是否正确", e); return true; } ServletUtil.write(response, JsonMessage.getString(IP_ACCESS_CODE, "Prohibition of access"), MediaType.APPLICATION_JSON_VALUE); diff --git a/modules/server/src/main/java/org/dromara/jpom/common/interceptor/PermissionInterceptor.java b/modules/server/src/main/java/org/dromara/jpom/common/interceptor/PermissionInterceptor.java index eff1efac2..cb3797e35 100644 --- a/modules/server/src/main/java/org/dromara/jpom/common/interceptor/PermissionInterceptor.java +++ b/modules/server/src/main/java/org/dromara/jpom/common/interceptor/PermissionInterceptor.java @@ -28,6 +28,7 @@ import cn.hutool.extra.servlet.ServletUtil; import cn.hutool.extra.spring.SpringUtil; import cn.keepbx.jpom.model.JsonMessage; import org.dromara.jpom.common.BaseServerController; +import org.dromara.jpom.exception.AgentException; import org.dromara.jpom.model.BaseNodeModel; import org.dromara.jpom.model.data.NodeModel; import org.dromara.jpom.model.user.UserBindWorkspaceModel; @@ -37,7 +38,6 @@ import org.dromara.jpom.service.h2db.BaseNodeService; import org.dromara.jpom.service.h2db.BaseWorkspaceService; import org.dromara.jpom.service.node.NodeService; import org.dromara.jpom.service.user.UserBindWorkspaceService; -import org.dromara.jpom.system.AgentException; import org.springframework.context.annotation.Configuration; import org.springframework.http.MediaType; import org.springframework.web.method.HandlerMethod; @@ -181,21 +181,9 @@ public class PermissionInterceptor implements HandlerMethodInterceptor { this.errorMsg(response, "您不是超级管理员没有权限:-2"); return false; } - NodeModel node = (NodeModel) request.getAttribute("node"); - if (node == null) { - // 服务端 - if (!userModel.isSystemUser()) { - this.errorMsg(response, "您没有服务端管理权限:-2"); - return false; - } - } else { - // 判断节点管理权限 - String workspaceId = BaseWorkspaceService.getWorkspaceId(request); - UserBindWorkspaceModel.PermissionResult permissionResult = userBindWorkspaceService.checkPermission(userModel, workspaceId + UserBindWorkspaceService.SYSTEM_USER); - if (!permissionResult.isSuccess()) { - this.errorMsg(response, permissionResult.errorMsg("节点管理")); - return false; - } + if (!userModel.isSystemUser()) { + this.errorMsg(response, "您没有服务端管理权限:-2"); + return false; } return true; } diff --git a/modules/server/src/main/java/org/dromara/jpom/configuration/FileStorageConfig.java b/modules/server/src/main/java/org/dromara/jpom/configuration/FileStorageConfig.java index c809a3fe2..e09935b9d 100644 --- a/modules/server/src/main/java/org/dromara/jpom/configuration/FileStorageConfig.java +++ b/modules/server/src/main/java/org/dromara/jpom/configuration/FileStorageConfig.java @@ -39,4 +39,18 @@ public class FileStorageConfig { * 文件中心存储路径 */ private String savePah; + /** + * 静态目录扫描周期 + *

+ * 0 0/1 * * * + */ + private String scanStaticDirCron = "0 0/1 * * *"; + /** + * 开启静态目录监听 + */ + private Boolean watchMonitorStaticDir = true; + /** + * 监听深度 + */ + private Integer watchMonitorMaxDepth = 1; } diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/DataStatController.java b/modules/server/src/main/java/org/dromara/jpom/controller/DataStatController.java new file mode 100644 index 000000000..bb4dca507 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/controller/DataStatController.java @@ -0,0 +1,113 @@ +/* + * 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.controller; + +import cn.hutool.db.Entity; +import cn.keepbx.jpom.IJsonMessage; +import cn.keepbx.jpom.model.JsonMessage; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.func.files.service.FileStorageService; +import org.dromara.jpom.func.files.service.StaticFileStorageService; +import org.dromara.jpom.service.docker.DockerInfoService; +import org.dromara.jpom.service.docker.DockerSwarmInfoService; +import org.dromara.jpom.service.node.NodeService; +import org.dromara.jpom.service.node.ProjectInfoCacheService; +import org.dromara.jpom.service.node.script.NodeScriptServer; +import org.dromara.jpom.service.node.ssh.SshCommandService; +import org.dromara.jpom.service.node.ssh.SshService; +import org.dromara.jpom.service.outgiving.OutGivingServer; +import org.dromara.jpom.service.script.ScriptServer; +import org.springframework.http.MediaType; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; +import java.util.HashMap; +import java.util.Map; + +/** + * @author bwcx_jzy + * @since 24/1/4 004 + */ +@RestController +@RequestMapping(value = "stat") +@Slf4j +public class DataStatController { + + private final NodeService nodeService; + private final ProjectInfoCacheService projectInfoCacheService; + private final NodeScriptServer nodeScriptServer; + private final OutGivingServer outGivingServer; + private final SshService sshService; + private final SshCommandService sshCommandService; + private final ScriptServer scriptServer; + private final DockerInfoService dockerInfoService; + private final FileStorageService fileStorageService; + private final StaticFileStorageService staticFileStorageService; + private final DockerSwarmInfoService dockerSwarmInfoService; + + public DataStatController(NodeService nodeService, + ProjectInfoCacheService projectInfoCacheService, + NodeScriptServer nodeScriptServer, + OutGivingServer outGivingServer, + SshService sshService, + SshCommandService sshCommandService, + ScriptServer scriptServer, + DockerInfoService dockerInfoService, + FileStorageService fileStorageService, + StaticFileStorageService staticFileStorageService, + DockerSwarmInfoService dockerSwarmInfoService) { + this.nodeService = nodeService; + this.projectInfoCacheService = projectInfoCacheService; + this.nodeScriptServer = nodeScriptServer; + this.outGivingServer = outGivingServer; + this.sshService = sshService; + this.sshCommandService = sshCommandService; + this.scriptServer = scriptServer; + this.dockerInfoService = dockerInfoService; + this.fileStorageService = fileStorageService; + this.staticFileStorageService = staticFileStorageService; + this.dockerSwarmInfoService = dockerSwarmInfoService; + } + + @RequestMapping(value = "workspace", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage> workspace(HttpServletRequest request) { + String workspaceId = nodeService.getCheckUserWorkspace(request); + Entity entity = Entity.create(); + entity.set("workspaceId", workspaceId); + Map map = new HashMap<>(10); + map.put("nodeCount", nodeService.count(entity)); + map.put("projectCount", projectInfoCacheService.count(entity)); + map.put("nodeScriptCount", nodeScriptServer.count(entity)); + map.put("outGivingCount", outGivingServer.count(entity)); + map.put("sshCount", sshService.count(entity)); + map.put("sshCommandCount", sshCommandService.count(entity)); + map.put("scriptCount", scriptServer.count(entity)); + map.put("dockerCount", dockerInfoService.count(entity)); + map.put("dockerSwarmCount", dockerSwarmInfoService.count(entity)); + map.put("fileCount", fileStorageService.count(entity)); + //map.put("staticFileCount", staticFileStorageService.count(entity)); + return JsonMessage.success("", map); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/IndexControl.java b/modules/server/src/main/java/org/dromara/jpom/controller/IndexControl.java index ad2c01378..34acbc572 100644 --- a/modules/server/src/main/java/org/dromara/jpom/controller/IndexControl.java +++ b/modules/server/src/main/java/org/dromara/jpom/controller/IndexControl.java @@ -298,33 +298,26 @@ public class IndexControl extends BaseServerController { */ @RequestMapping(value = "menus_data.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) public IJsonMessage> menusData(HttpServletRequest request) { - NodeModel nodeModel = tryGetNode(); UserModel userModel = getUserModel(); String workspaceId = nodeService.getCheckUserWorkspace(request); JSONObject config = systemParametersServer.getConfigDefNewInstance(StrUtil.format("menus_config_{}", workspaceId), JSONObject.class); // 菜单 - InputStream inputStream; - JSONArray showArray; - if (nodeModel == null) { - inputStream = ResourceUtil.getStream("classpath:/menus/index.json"); - showArray = config.getJSONArray("serverMenuKeys"); - } else { - inputStream = ResourceUtil.getStream("classpath:/menus/node-index.json"); - showArray = config.getJSONArray("nodeMenuKeys"); - } + InputStream inputStream = ResourceUtil.getStream("classpath:/menus/index.json"); + JSONArray showArray = config.getJSONArray("serverMenuKeys"); + String json = IoUtil.read(inputStream, CharsetUtil.CHARSET_UTF_8); JSONArray jsonArray = JSONArray.parseArray(json); List collect1 = jsonArray.stream().filter(o -> { JSONObject jsonObject = (JSONObject) o; - if (!testMenus(jsonObject, userModel, nodeModel, showArray, request)) { + if (!testMenus(jsonObject, userModel, showArray, request)) { return false; } JSONArray childs = jsonObject.getJSONArray("childs"); if (childs != null) { List collect = childs.stream().filter(o1 -> { JSONObject jsonObject1 = (JSONObject) o1; - return testMenus(jsonObject1, userModel, nodeModel, showArray, request); + return testMenus(jsonObject1, userModel, showArray, request); }).collect(Collectors.toList()); if (collect.isEmpty()) { return false; @@ -355,14 +348,14 @@ public class IndexControl extends BaseServerController { JSONArray jsonArray = JSONArray.parseArray(json); List collect1 = jsonArray.stream().filter(o -> { JSONObject jsonObject = (JSONObject) o; - if (!testMenus(jsonObject, userModel, null, null, request)) { + if (!testMenus(jsonObject, userModel, null, request)) { return false; } JSONArray childs = jsonObject.getJSONArray("childs"); if (childs != null) { List collect = childs.stream().filter(o1 -> { JSONObject jsonObject1 = (JSONObject) o1; - return testMenus(jsonObject1, userModel, null, null, request); + return testMenus(jsonObject1, userModel, null, request); }).collect(Collectors.toList()); if (collect.isEmpty()) { return false; @@ -375,7 +368,7 @@ public class IndexControl extends BaseServerController { return JsonMessage.success("", collect1); } - private boolean testMenus(JSONObject jsonObject, UserModel userModel, NodeModel nodeModel, JSONArray showArray, HttpServletRequest request) { + private boolean testMenus(JSONObject jsonObject, UserModel userModel, JSONArray showArray, HttpServletRequest request) { String storageMode = jsonObject.getString("storageMode"); if (StrUtil.isNotEmpty(storageMode)) { if (!StrUtil.equals(dbExtConfig.getMode().name(), storageMode)) { @@ -403,15 +396,7 @@ public class IndexControl extends BaseServerController { // 系统管理员权限 boolean system = StrUtil.equals(role, "system"); if (system) { - if (nodeModel == null) { - return userModel.isSystemUser(); - } else { - if (userModel.isSuperSystemUser()) { - return true; - } - String workspaceId = BaseWorkspaceService.getWorkspaceId(request); - return userBindWorkspaceService.exists(userModel, workspaceId + UserBindWorkspaceService.SYSTEM_USER); - } + return userModel.isSystemUser(); } return true; } diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/LoginControl.java b/modules/server/src/main/java/org/dromara/jpom/controller/LoginControl.java index 8285a1ce6..ac4b4f020 100644 --- a/modules/server/src/main/java/org/dromara/jpom/controller/LoginControl.java +++ b/modules/server/src/main/java/org/dromara/jpom/controller/LoginControl.java @@ -88,7 +88,7 @@ import java.util.concurrent.TimeUnit; @Slf4j public class LoginControl extends BaseServerController implements InitializingBean { /** - * ip 黑名单 + * ip 禁止缓存 */ public static final LFUCache LFU_CACHE = new LFUCache<>(1000); /** @@ -120,19 +120,24 @@ public class LoginControl extends BaseServerController implements InitializingBe */ @RequestMapping(value = "rand-code", method = RequestMethod.GET, produces = MediaType.APPLICATION_JSON_VALUE) @NotLogin - public IJsonMessage randCode() { + public IJsonMessage randCode(String theme) { if (webConfig.isDisabledCaptcha()) { return new JsonMessage<>(400, "验证码已禁用"); } - AbstractCaptcha captcha = this.createCaptcha(); + CircleCaptcha captcha = this.createCaptcha(theme); setSessionAttribute(LOGIN_CODE, captcha.getCode()); String base64Data = captcha.getImageBase64Data(); return new JsonMessage<>(200, "", base64Data); } - private CircleCaptcha createCaptcha() { + private CircleCaptcha createCaptcha(String theme) { int height = 50; CircleCaptcha circleCaptcha = new CircleCaptcha(100, height, 4, 8); + if (StrUtil.equalsIgnoreCase(theme, "dark")) { + circleCaptcha.setBackground(Color.darkGray); + } else { + circleCaptcha.setBackground(Color.white); + } // 设置为默认字体 circleCaptcha.setFont(new Font(null, Font.PLAIN, (int) (height * 0.75))); circleCaptcha.createCode(); @@ -198,7 +203,7 @@ public class LoginControl extends BaseServerController implements InitializingBe userLoginLogServer.fail(userModel, 4, false, request); return new JsonMessage<>(ServerConst.ACCOUNT_LOCKED, ServerConst.ACCOUNT_LOCKED_TIP); } - if (!webConfig.isDisabledGuide()) { + if (!webConfig.isDisabledCaptcha()) { // 获取验证码 String sCode = getSessionAttribute(LOGIN_CODE); Assert.state(StrUtil.equalsIgnoreCase(code, sCode), "请输入正确的验证码"); @@ -438,7 +443,7 @@ public class LoginControl extends BaseServerController implements InitializingBe @Override public void afterPropertiesSet() throws Exception { try { - this.createCaptcha(); + this.createCaptcha(null); log.debug("当前服务器验证码可用"); } catch (Throwable e) { log.warn("当前服务器生成验证码异常,自动禁用验证码", e); diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/build/BuildInfoController.java b/modules/server/src/main/java/org/dromara/jpom/controller/build/BuildInfoController.java index 2e736a206..3a924b3e4 100644 --- a/modules/server/src/main/java/org/dromara/jpom/controller/build/BuildInfoController.java +++ b/modules/server/src/main/java/org/dromara/jpom/controller/build/BuildInfoController.java @@ -472,6 +472,12 @@ public class BuildInfoController extends BaseServerController { @PostMapping(value = "/build/delete", produces = MediaType.APPLICATION_JSON_VALUE) @Feature(method = MethodFeature.DEL) public IJsonMessage delete(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "没有数据id") String id, HttpServletRequest request) { + this.delById(id, request); + return JsonMessage.success("删除成功,并且清理历史构建产物成功"); + } + + + private void delById(String id, HttpServletRequest request) { // 查询构建信息 BuildInfoModel buildInfoModel = buildInfoService.getByKey(id, request); Objects.requireNonNull(buildInfoModel, "没有对应数据"); @@ -488,6 +494,21 @@ public class BuildInfoController extends BaseServerController { Assert.state(!fastDel, "清理历史构建产物失败,已经重新尝试"); // 删除构建信息数据 buildInfoService.delByKey(buildInfoModel.getId(), request); + } + + /** + * 批量删除构建信息 + * + * @param ids 构建ID + * @return json + */ + @PostMapping(value = "/build/batch-delete", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.DEL) + public IJsonMessage batchDelete(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "没有数据id") String ids, HttpServletRequest request) { + List list = StrUtil.splitTrim(ids, StrUtil.COMMA); + for (String s : list) { + this.delById(s, request); + } return JsonMessage.success("删除成功,并且清理历史构建产物成功"); } diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/build/BuildInfoHistoryController.java b/modules/server/src/main/java/org/dromara/jpom/controller/build/BuildInfoHistoryController.java index acbfb787e..266b1afba 100644 --- a/modules/server/src/main/java/org/dromara/jpom/controller/build/BuildInfoHistoryController.java +++ b/modules/server/src/main/java/org/dromara/jpom/controller/build/BuildInfoHistoryController.java @@ -77,7 +77,7 @@ public class BuildInfoHistoryController extends BaseServerController { * * @param logId 日志id */ - @RequestMapping(value = "/build/history/download_file.html", method = RequestMethod.GET) + @RequestMapping(value = "/build/history/download_file", method = RequestMethod.GET) @Feature(method = MethodFeature.DOWNLOAD) public void downloadFile(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "没有数据") String logId, HttpServletRequest request, HttpServletResponse response) { BuildHistoryLog buildHistoryLog = dbBuildHistoryLogService.getByKey(logId, request, false); @@ -94,8 +94,9 @@ public class BuildInfoHistoryController extends BaseServerController { @Feature(method = MethodFeature.DOWNLOAD) public void downloadFile(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "没有数据") String buildId, @ValidatorItem(ValidatorRule.NUMBERS) int buildNumberId, - HttpServletResponse response) { - String workspaceId = dbBuildHistoryLogService.getCheckUserWorkspace(getRequest()); + HttpServletResponse response, + HttpServletRequest request) { + String workspaceId = dbBuildHistoryLogService.getCheckUserWorkspace(request); // BuildHistoryLog historyLog = new BuildHistoryLog(); historyLog.setWorkspaceId(workspaceId); @@ -119,7 +120,7 @@ public class BuildInfoHistoryController extends BaseServerController { } - @RequestMapping(value = "/build/history/download_log.html", method = RequestMethod.GET) + @RequestMapping(value = "/build/history/download_log", method = RequestMethod.GET) @Feature(method = MethodFeature.DOWNLOAD) public void downloadLog(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "没有数据") String logId, HttpServletRequest request, HttpServletResponse response) { BuildHistoryLog buildHistoryLog = dbBuildHistoryLogService.getByKey(logId, request); @@ -137,8 +138,8 @@ public class BuildInfoHistoryController extends BaseServerController { @RequestMapping(value = "/build/history/history_list.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @Feature(method = MethodFeature.LIST) - public IJsonMessage> historyList() { - PageResultDto pageResultTemp = dbBuildHistoryLogService.listPage(getRequest()); + public IJsonMessage> historyList(HttpServletRequest request) { + PageResultDto pageResultTemp = dbBuildHistoryLogService.listPage(request); pageResultTemp.each(buildHistoryLog -> { File file = BuildUtil.getHistoryPackageFile(buildHistoryLog.getBuildDataId(), buildHistoryLog.getBuildNumberId(), buildHistoryLog.getResultDirFile()); buildHistoryLog.setHasFile(FileUtil.isNotEmpty(file)); @@ -157,10 +158,10 @@ public class BuildInfoHistoryController extends BaseServerController { */ @RequestMapping(value = "/build/history/delete_log.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @Feature(method = MethodFeature.DEL) - public IJsonMessage delete(@ValidatorConfig(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "没有数据")) String logId) { + public IJsonMessage delete(@ValidatorConfig(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "没有数据")) String logId, HttpServletRequest request) { List strings = StrUtil.splitTrim(logId, StrUtil.COMMA); for (String itemId : strings) { - BuildHistoryLog buildHistoryLog = dbBuildHistoryLogService.getByKey(itemId, getRequest()); + BuildHistoryLog buildHistoryLog = dbBuildHistoryLogService.getByKey(itemId, request); IJsonMessage jsonMessage = dbBuildHistoryLogService.deleteLogAndFile(buildHistoryLog); if (!jsonMessage.success()) { return jsonMessage; diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/build/BuildInfoManageController.java b/modules/server/src/main/java/org/dromara/jpom/controller/build/BuildInfoManageController.java index a3a7c95c7..2c880d2e2 100644 --- a/modules/server/src/main/java/org/dromara/jpom/controller/build/BuildInfoManageController.java +++ b/modules/server/src/main/java/org/dromara/jpom/controller/build/BuildInfoManageController.java @@ -97,8 +97,9 @@ public class BuildInfoManageController extends BaseServerController { String checkRepositoryDiff, String projectSecondaryDirectory, String buildEnvParameter, - String dispatchSelectProject) { - BuildInfoModel item = buildInfoService.getByKey(id, getRequest()); + String dispatchSelectProject, + HttpServletRequest request) { + BuildInfoModel item = buildInfoService.getByKey(id, request); Assert.notNull(item, "没有对应数据"); // 更新数据 BuildInfoModel update = new BuildInfoModel(); @@ -136,8 +137,8 @@ public class BuildInfoManageController extends BaseServerController { */ @RequestMapping(value = "/build/manage/cancel", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @Feature(method = MethodFeature.EXECUTE) - public IJsonMessage cancel(@ValidatorConfig(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "没有数据")) String id) { - BuildInfoModel item = buildInfoService.getByKey(id, getRequest()); + public IJsonMessage cancel(@ValidatorConfig(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "没有数据")) String id, HttpServletRequest request) { + BuildInfoModel item = buildInfoService.getByKey(id, request); Objects.requireNonNull(item, "没有对应数据"); String checkStatus = buildExecuteService.checkStatus(item); BuildStatus nowStatus = BaseEnum.getEnum(BuildStatus.class, item.getStatus()); @@ -184,8 +185,9 @@ public class BuildInfoManageController extends BaseServerController { @Feature(method = MethodFeature.LIST) public IJsonMessage getNowLog(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "没有数据") String id, @ValidatorItem(value = ValidatorRule.POSITIVE_INTEGER, msg = "没有buildId") int buildId, - @ValidatorItem(value = ValidatorRule.POSITIVE_INTEGER, msg = "line") int line) { - BuildInfoModel item = buildInfoService.getByKey(id, getRequest()); + @ValidatorItem(value = ValidatorRule.POSITIVE_INTEGER, msg = "line") int line, + HttpServletRequest request) { + BuildInfoModel item = buildInfoService.getByKey(id, request); Assert.notNull(item, "没有对应数据"); Assert.state(buildId <= item.getBuildId(), "还没有对应的构建记录"); diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/build/BuildInfoTriggerController.java b/modules/server/src/main/java/org/dromara/jpom/controller/build/BuildInfoTriggerController.java index 121392355..180c61420 100644 --- a/modules/server/src/main/java/org/dromara/jpom/controller/build/BuildInfoTriggerController.java +++ b/modules/server/src/main/java/org/dromara/jpom/controller/build/BuildInfoTriggerController.java @@ -42,6 +42,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; +import javax.servlet.http.HttpServletRequest; import java.util.HashMap; import java.util.Map; @@ -72,8 +73,8 @@ public class BuildInfoTriggerController extends BaseServerController { */ @RequestMapping(value = "/build/trigger/url", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @Feature(method = MethodFeature.EDIT) - public IJsonMessage> getTriggerUrl(String id, String rest) { - BuildInfoModel item = buildInfoService.getByKey(id, getRequest()); + public IJsonMessage> getTriggerUrl(String id, String rest, HttpServletRequest request) { + BuildInfoModel item = buildInfoService.getByKey(id, request); UserModel user = getUser(); BuildInfoModel updateInfo; if (StrUtil.isEmpty(item.getTriggerToken()) || StrUtil.isNotEmpty(rest)) { @@ -85,12 +86,12 @@ public class BuildInfoTriggerController extends BaseServerController { } else { updateInfo = item; } - Map map = this.getBuildToken(updateInfo); + Map map = this.getBuildToken(updateInfo, request); return JsonMessage.success(StrUtil.isEmpty(rest) ? "ok" : "重置成功", map); } - private Map getBuildToken(BuildInfoModel item) { - String contextPath = UrlRedirectUtil.getHeaderProxyPath(getRequest(), ServerConst.PROXY_PATH); + private Map getBuildToken(BuildInfoModel item, HttpServletRequest request) { + String contextPath = UrlRedirectUtil.getHeaderProxyPath(request, ServerConst.PROXY_PATH); String url = ServerOpenApi.BUILD_TRIGGER_BUILD2. replace("{id}", item.getId()). replace("{token}", item.getTriggerToken()); diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/docker/DockerInfoController.java b/modules/server/src/main/java/org/dromara/jpom/controller/docker/DockerInfoController.java index 9f3d7ba44..95b088bec 100644 --- a/modules/server/src/main/java/org/dromara/jpom/controller/docker/DockerInfoController.java +++ b/modules/server/src/main/java/org/dromara/jpom/controller/docker/DockerInfoController.java @@ -133,11 +133,25 @@ public class DockerInfoController extends BaseServerController { @GetMapping(value = "sync-to-workspace", produces = MediaType.APPLICATION_JSON_VALUE) @Feature(method = MethodFeature.EDIT) @SystemPermission() - public IJsonMessage syncToWorkspace(@ValidatorItem String ids, @ValidatorItem String toWorkspaceId) { - String nowWorkspaceId = dockerInfoService.getCheckUserWorkspace(getRequest()); + public IJsonMessage syncToWorkspace(@ValidatorItem String ids, @ValidatorItem String toWorkspaceId, HttpServletRequest request) { + String nowWorkspaceId = dockerInfoService.getCheckUserWorkspace(request); // dockerInfoService.checkUserWorkspace(toWorkspaceId); dockerInfoService.syncToWorkspace(ids, nowWorkspaceId, toWorkspaceId); return JsonMessage.success("操作成功"); } + + /** + * 查询所有的 tag + * + * @return msg + */ + @GetMapping(value = "all-tag", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> allTag(HttpServletRequest request) { + String workspaceId = dockerInfoService.getCheckUserWorkspace(request); + // + List strings = dockerInfoService.allTag(workspaceId); + return JsonMessage.success("", strings); + } } diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/docker/base/BaseDockerImagesController.java b/modules/server/src/main/java/org/dromara/jpom/controller/docker/base/BaseDockerImagesController.java index 629c435bd..5afaeaf70 100644 --- a/modules/server/src/main/java/org/dromara/jpom/controller/docker/base/BaseDockerImagesController.java +++ b/modules/server/src/main/java/org/dromara/jpom/controller/docker/base/BaseDockerImagesController.java @@ -23,6 +23,7 @@ package org.dromara.jpom.controller.docker.base; import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IoUtil; import cn.hutool.core.lang.Tuple; import cn.hutool.core.thread.ThreadUtil; import cn.hutool.core.util.IdUtil; @@ -150,10 +151,13 @@ public abstract class BaseDockerImagesController extends BaseDockerController { ThreadUtil.execute(() -> { try { plugin.execute("pullImage", parameter); + logRecorder.system("pull end"); } catch (Exception e) { logRecorder.error("拉取异常", e); + } finally { + IoUtil.close(logRecorder); } - logRecorder.system("pull end"); + }); return JsonMessage.success("开始拉取", uuid); } diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/docker/base/BaseDockerSwarmServiceController.java b/modules/server/src/main/java/org/dromara/jpom/controller/docker/base/BaseDockerSwarmServiceController.java index d30b3645d..f04d0c548 100644 --- a/modules/server/src/main/java/org/dromara/jpom/controller/docker/base/BaseDockerSwarmServiceController.java +++ b/modules/server/src/main/java/org/dromara/jpom/controller/docker/base/BaseDockerSwarmServiceController.java @@ -25,6 +25,7 @@ package org.dromara.jpom.controller.docker.base; import cn.hutool.cache.impl.TimedCache; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IoUtil; import cn.hutool.core.map.MapUtil; import cn.hutool.core.thread.ThreadUtil; import cn.hutool.core.util.CharsetUtil; @@ -181,10 +182,12 @@ public abstract class BaseDockerSwarmServiceController extends BaseDockerControl ThreadUtil.execute(() -> { try { plugin.execute(StrUtil.equalsIgnoreCase(type, "service") ? "logService" : "logTask", parameter); + logRecorder.system("pull end"); } catch (Exception e) { logRecorder.error("拉取日志异常", e); + } finally { + IoUtil.close(logRecorder); } - logRecorder.system("pull end"); }); // 添加到缓存中 LOG_CACHE.put(uuid, CollUtil.newHashSet(getUser().getId())); diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/monitor/MonitorListController.java b/modules/server/src/main/java/org/dromara/jpom/controller/monitor/MonitorListController.java index dac2bf24d..0145925ee 100644 --- a/modules/server/src/main/java/org/dromara/jpom/controller/monitor/MonitorListController.java +++ b/modules/server/src/main/java/org/dromara/jpom/controller/monitor/MonitorListController.java @@ -49,7 +49,6 @@ import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; -import java.sql.SQLException; import java.util.List; /** @@ -85,8 +84,8 @@ public class MonitorListController extends BaseServerController { */ @RequestMapping(value = "getMonitorList", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @Feature(method = MethodFeature.LIST) - public IJsonMessage> getMonitorList() { - PageResultDto pageResultDto = monitorService.listPage(getRequest()); + public IJsonMessage> getMonitorList(HttpServletRequest request) { + PageResultDto pageResultDto = monitorService.listPage(request); return JsonMessage.success("", pageResultDto); } @@ -98,9 +97,9 @@ public class MonitorListController extends BaseServerController { */ @RequestMapping(value = "deleteMonitor", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @Feature(method = MethodFeature.DEL) - public IJsonMessage deleteMonitor(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "删除失败") String id) throws SQLException { + public IJsonMessage deleteMonitor(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "删除失败") String id, HttpServletRequest request) { // - HttpServletRequest request = getRequest(); + int delByKey = monitorService.delByKey(id, request); if (delByKey > 0) { // 删除日志 @@ -121,9 +120,10 @@ public class MonitorListController extends BaseServerController { @RequestMapping(value = "updateMonitor", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @Feature(method = MethodFeature.EDIT) public IJsonMessage updateMonitor(String id, - @ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "监控名称不能为空") String name, - @ValidatorItem(msg = "请配置监控周期") String execCron, - String notifyUser, String webhook) { + @ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "监控名称不能为空") String name, + @ValidatorItem(msg = "请配置监控周期") String execCron, + String notifyUser, String webhook, + HttpServletRequest request) { String status = getParameter("status"); String autoRestart = getParameter("autoRestart"); @@ -171,7 +171,7 @@ public class MonitorListController extends BaseServerController { monitorService.insert(monitorModel); return JsonMessage.success("添加成功"); } - HttpServletRequest request = getRequest(); + monitorService.updateById(monitorModel, request); return JsonMessage.success("修改成功"); } diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/monitor/MonitorLogController.java b/modules/server/src/main/java/org/dromara/jpom/controller/monitor/MonitorLogController.java index 5c4381aea..9033f6d93 100644 --- a/modules/server/src/main/java/org/dromara/jpom/controller/monitor/MonitorLogController.java +++ b/modules/server/src/main/java/org/dromara/jpom/controller/monitor/MonitorLogController.java @@ -36,6 +36,8 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; +import javax.servlet.http.HttpServletRequest; + /** * 监控列表 * @@ -60,8 +62,8 @@ public class MonitorLogController extends BaseServerController { */ @RequestMapping(value = "list_data.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @Feature(method = MethodFeature.LIST) - public IJsonMessage> listData() { - PageResultDto pageResult = dbMonitorNotifyLogService.listPage(getRequest()); + public IJsonMessage> listData(HttpServletRequest request) { + PageResultDto pageResult = dbMonitorNotifyLogService.listPage(request); return JsonMessage.success("获取成功", pageResult); } } diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/monitor/MonitorUserOptListController.java b/modules/server/src/main/java/org/dromara/jpom/controller/monitor/MonitorUserOptListController.java index 71ae084c9..f723b9dd7 100644 --- a/modules/server/src/main/java/org/dromara/jpom/controller/monitor/MonitorUserOptListController.java +++ b/modules/server/src/main/java/org/dromara/jpom/controller/monitor/MonitorUserOptListController.java @@ -44,6 +44,7 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; +import javax.servlet.http.HttpServletRequest; import java.util.Arrays; import java.util.List; import java.util.Objects; @@ -69,8 +70,8 @@ public class MonitorUserOptListController extends BaseServerController { @RequestMapping(value = "list_data", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @Feature(method = MethodFeature.LIST) - public IJsonMessage> getMonitorList() { - PageResultDto pageResultDto = monitorUserOptService.listPage(getRequest()); + public IJsonMessage> getMonitorList(HttpServletRequest request) { + PageResultDto pageResultDto = monitorUserOptService.listPage(request); return JsonMessage.success("", pageResultDto); } @@ -117,9 +118,9 @@ public class MonitorUserOptListController extends BaseServerController { */ @RequestMapping(value = "delete", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @Feature(method = MethodFeature.DEL) - public IJsonMessage deleteMonitor(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "删除失败") String id) { + public IJsonMessage deleteMonitor(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "删除失败") String id, HttpServletRequest request) { // - monitorUserOptService.delByKey(id, getRequest()); + monitorUserOptService.delByKey(id, request); return JsonMessage.success("删除成功"); } @@ -135,11 +136,11 @@ public class MonitorUserOptListController extends BaseServerController { @RequestMapping(value = "update", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @Feature(method = MethodFeature.EDIT) public IJsonMessage updateMonitor(String id, - @ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "监控名称不能为空") String name, - String notifyUser, - String monitorUser, - String monitorOpt, - String monitorFeature) { + @ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "监控名称不能为空") String name, + String notifyUser, + String monitorUser, + String monitorOpt, + String monitorFeature) { String status = getParameter("status"); @@ -210,7 +211,7 @@ public class MonitorUserOptListController extends BaseServerController { @RequestMapping(value = "changeStatus", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @Feature(method = MethodFeature.EDIT) public IJsonMessage changeStatus(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "id不能为空") String id, - String status) { + String status) { MonitorUserOptModel monitorModel = monitorUserOptService.getByKey(id); Assert.notNull(monitorModel, "不存在监控项啦"); diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/node/NodeEditController.java b/modules/server/src/main/java/org/dromara/jpom/controller/node/NodeEditController.java index b83b172b3..892b4650b 100644 --- a/modules/server/src/main/java/org/dromara/jpom/controller/node/NodeEditController.java +++ b/modules/server/src/main/java/org/dromara/jpom/controller/node/NodeEditController.java @@ -131,8 +131,7 @@ public class NodeEditController extends BaseServerController { */ @PostMapping(value = "del.json", produces = MediaType.APPLICATION_JSON_VALUE) @Feature(method = MethodFeature.DEL) - public IJsonMessage del(@ValidatorItem String id) { - HttpServletRequest request = getRequest(); + public IJsonMessage del(@ValidatorItem String id, HttpServletRequest request) { this.checkDataBind(id, request, "删除"); // { @@ -185,8 +184,7 @@ public class NodeEditController extends BaseServerController { */ @GetMapping(value = "unbind.json", produces = MediaType.APPLICATION_JSON_VALUE) @Feature(method = MethodFeature.DEL) - public IJsonMessage unbind(String id) { - HttpServletRequest request = getRequest(); + public IJsonMessage unbind(String id, HttpServletRequest request) { this.checkDataBind(id, request, "解绑"); // projectInfoCacheService.delCache(id, request); @@ -206,8 +204,8 @@ public class NodeEditController extends BaseServerController { @GetMapping(value = "sync-to-workspace", produces = MediaType.APPLICATION_JSON_VALUE) @Feature(method = MethodFeature.EDIT) @SystemPermission() - public IJsonMessage syncToWorkspace(@ValidatorItem String ids, @ValidatorItem String toWorkspaceId) { - String nowWorkspaceId = nodeService.getCheckUserWorkspace(getRequest()); + public IJsonMessage syncToWorkspace(@ValidatorItem String ids, @ValidatorItem String toWorkspaceId, HttpServletRequest request) { + String nowWorkspaceId = nodeService.getCheckUserWorkspace(request); // nodeService.checkUserWorkspace(toWorkspaceId); nodeService.syncToWorkspace(ids, nowWorkspaceId, toWorkspaceId); @@ -224,8 +222,7 @@ public class NodeEditController extends BaseServerController { */ @GetMapping(value = "sort-item", produces = MediaType.APPLICATION_JSON_VALUE) @Feature(method = MethodFeature.EDIT) - public IJsonMessage sortItem(@ValidatorItem String id, @ValidatorItem String method, String compareId) { - HttpServletRequest request = getRequest(); + public IJsonMessage sortItem(@ValidatorItem String id, @ValidatorItem String method, String compareId, HttpServletRequest request) { if (StrUtil.equalsIgnoreCase(method, "top")) { nodeService.sortToTop(id, request); } else if (StrUtil.equalsIgnoreCase(method, "up")) { diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/node/NodeProjectInfoController.java b/modules/server/src/main/java/org/dromara/jpom/controller/node/NodeProjectInfoController.java index 5a89bff80..f03440725 100644 --- a/modules/server/src/main/java/org/dromara/jpom/controller/node/NodeProjectInfoController.java +++ b/modules/server/src/main/java/org/dromara/jpom/controller/node/NodeProjectInfoController.java @@ -24,7 +24,6 @@ package org.dromara.jpom.controller.node; import cn.hutool.core.io.FileUtil; import cn.hutool.core.util.StrUtil; -import cn.hutool.db.Entity; import cn.keepbx.jpom.IJsonMessage; import cn.keepbx.jpom.model.JsonMessage; import lombok.extern.slf4j.Slf4j; @@ -40,7 +39,6 @@ import org.dromara.jpom.model.user.UserModel; import org.dromara.jpom.permission.ClassFeature; import org.dromara.jpom.permission.Feature; import org.dromara.jpom.permission.MethodFeature; -import org.dromara.jpom.permission.SystemPermission; import org.dromara.jpom.service.node.ProjectInfoCacheService; import org.dromara.jpom.service.user.TriggerTokenLogServer; import org.springframework.http.MediaType; @@ -73,18 +71,6 @@ public class NodeProjectInfoController extends BaseServerController { this.triggerTokenLogServer = triggerTokenLogServer; } - /** - * @return json - * @author Hotstrip - * load node project list - * 加载节点项目列表 - */ - @PostMapping(value = "node_project_list", produces = MediaType.APPLICATION_JSON_VALUE) - public IJsonMessage> nodeProjectList() { - PageResultDto resultDto = projectInfoCacheService.listPageNode(getRequest()); - return JsonMessage.success("success", resultDto); - } - /** * load node project list @@ -94,8 +80,8 @@ public class NodeProjectInfoController extends BaseServerController { * @author Hotstrip */ @PostMapping(value = "project_list", produces = MediaType.APPLICATION_JSON_VALUE) - public IJsonMessage> projectList() { - PageResultDto resultDto = projectInfoCacheService.listPage(getRequest()); + public IJsonMessage> projectList(HttpServletRequest request) { + PageResultDto resultDto = projectInfoCacheService.listPage(request); return JsonMessage.success("success", resultDto); } @@ -107,8 +93,8 @@ public class NodeProjectInfoController extends BaseServerController { * @author Hotstrip */ @GetMapping(value = "project_list_all", produces = MediaType.APPLICATION_JSON_VALUE) - public IJsonMessage> projectListAll() { - List projectInfoCacheModels = projectInfoCacheService.listByWorkspace(getRequest()); + public IJsonMessage> projectListAll(HttpServletRequest request) { + List projectInfoCacheModels = projectInfoCacheService.listByWorkspace(request); return JsonMessage.success("", projectInfoCacheModels); } @@ -131,29 +117,14 @@ public class NodeProjectInfoController extends BaseServerController { */ @GetMapping(value = "sync_project", produces = MediaType.APPLICATION_JSON_VALUE) @Feature(cls = ClassFeature.PROJECT, method = MethodFeature.DEL) - public IJsonMessage syncProject(String nodeId) { + public IJsonMessage syncProject(String nodeId, HttpServletRequest request) { NodeModel nodeModel = nodeService.getByKey(nodeId); Assert.notNull(nodeModel, "对应的节点不存在"); - int count = projectInfoCacheService.delCache(nodeId, getRequest()); + int count = projectInfoCacheService.delCache(nodeId, request); String msg = projectInfoCacheService.syncExecuteNode(nodeModel); return JsonMessage.success("主动清除:" + count + StrUtil.SPACE + msg); } - /** - * 删除节点缓存的所有项目 - * - * @return json - */ - @GetMapping(value = "clear_all_project", produces = MediaType.APPLICATION_JSON_VALUE) - @SystemPermission(superUser = true) - @Feature(cls = ClassFeature.PROJECT, method = MethodFeature.DEL) - public IJsonMessage clearAll() { - Entity where = Entity.create(); - where.set("id", " <> id"); - int del = projectInfoCacheService.del(where); - return JsonMessage.success("成功删除" + del + "条项目缓存"); - } - /** * 排序 * @@ -164,8 +135,7 @@ public class NodeProjectInfoController extends BaseServerController { */ @GetMapping(value = "project-sort-item", produces = MediaType.APPLICATION_JSON_VALUE) @Feature(method = MethodFeature.EDIT) - public IJsonMessage sortItem(@ValidatorItem String id, @ValidatorItem String method, String compareId) { - HttpServletRequest request = getRequest(); + public IJsonMessage sortItem(@ValidatorItem String id, @ValidatorItem String method, String compareId, HttpServletRequest request) { if (StrUtil.equalsIgnoreCase(method, "top")) { projectInfoCacheService.sortToTop(id, request); } else if (StrUtil.equalsIgnoreCase(method, "up")) { @@ -186,28 +156,28 @@ public class NodeProjectInfoController extends BaseServerController { */ @RequestMapping(value = "project-trigger-url", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @Feature(method = MethodFeature.EDIT) - public IJsonMessage> getTriggerUrl(String id, String rest) { - ProjectInfoCacheModel item = projectInfoCacheService.getByKey(id, getRequest()); + public IJsonMessage> getTriggerUrl(String id, String rest, HttpServletRequest request) { + ProjectInfoCacheModel item = projectInfoCacheService.getByKey(id, request); UserModel user = getUser(); ProjectInfoCacheModel updateItem; if (StrUtil.isEmpty(item.getTriggerToken()) || StrUtil.isNotEmpty(rest)) { updateItem = new ProjectInfoCacheModel(); updateItem.setId(id); updateItem.setTriggerToken(triggerTokenLogServer.restToken(item.getTriggerToken(), projectInfoCacheService.typeName(), - item.getId(), user.getId())); + item.getId(), user.getId())); projectInfoCacheService.updateById(updateItem); } else { updateItem = item; } - Map map = this.getBuildToken(updateItem); + Map map = this.getBuildToken(updateItem, request); return JsonMessage.success(StrUtil.isEmpty(rest) ? "ok" : "重置成功", map); } - private Map getBuildToken(ProjectInfoCacheModel item) { - String contextPath = UrlRedirectUtil.getHeaderProxyPath(getRequest(), ServerConst.PROXY_PATH); + private Map getBuildToken(ProjectInfoCacheModel item, HttpServletRequest request) { + String contextPath = UrlRedirectUtil.getHeaderProxyPath(request, ServerConst.PROXY_PATH); String url = ServerOpenApi.SERVER_PROJECT_TRIGGER_URL. - replace("{id}", item.getId()). - replace("{token}", item.getTriggerToken()); + replace("{id}", item.getId()). + replace("{token}", item.getTriggerToken()); String triggerBuildUrl = String.format("/%s/%s", contextPath, url); Map map = new HashMap<>(10); map.put("triggerUrl", FileUtil.normalize(triggerBuildUrl)); diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/node/NodeUpdateController.java b/modules/server/src/main/java/org/dromara/jpom/controller/node/NodeUpdateController.java index fad474806..bd64e1625 100644 --- a/modules/server/src/main/java/org/dromara/jpom/controller/node/NodeUpdateController.java +++ b/modules/server/src/main/java/org/dromara/jpom/controller/node/NodeUpdateController.java @@ -148,8 +148,8 @@ public class NodeUpdateController extends BaseServerController { @SystemPermission @Feature(method = MethodFeature.UPLOAD) public IJsonMessage uploadAgent(String sliceId, - Integer totalSlice, - String fileSumMd5) throws IOException { + Integer totalSlice, + String fileSumMd5) throws IOException { File agentPath = serverConfig.getAgentPath(); File userTempPath = serverConfig.getUserTempPath(); @@ -190,7 +190,7 @@ public class NodeUpdateController extends BaseServerController { } @GetMapping(value = "fast_install.json", produces = MediaType.APPLICATION_JSON_VALUE) - public IJsonMessage fastInstall() { + public IJsonMessage fastInstall(HttpServletRequest request) { boolean beta = RemoteVersion.betaRelease(); InputStream inputStream = ExtConfigBean.getConfigResourceInputStream(beta ? "/fast-install-beta.json" : "/fast-install-release.json"); String json = IoUtil.read(inputStream, CharsetUtil.CHARSET_UTF_8); @@ -202,7 +202,7 @@ public class NodeUpdateController extends BaseServerController { JSONArray jsonArray = JSONArray.parseArray(json); jsonObject.put("shUrls", jsonArray); // - String contextPath = UrlRedirectUtil.getHeaderProxyPath(getRequest(), ServerConst.PROXY_PATH); + String contextPath = UrlRedirectUtil.getHeaderProxyPath(request, ServerConst.PROXY_PATH); String url = String.format("/%s/%s", contextPath, ServerOpenApi.RECEIVE_PUSH); jsonObject.put("url", FileUtil.normalize(url)); return JsonMessage.success("", jsonObject); @@ -211,19 +211,21 @@ public class NodeUpdateController extends BaseServerController { @GetMapping(value = "pull_fast_install_result.json", produces = MediaType.APPLICATION_JSON_VALUE) public IJsonMessage> pullFastInstallResult(String removeId) { Collection jsonObjects = NodeInfoController.listReceiveCache(removeId); - jsonObjects = jsonObjects.stream().map(jsonObject -> { - JSONObject clone = jsonObject.clone(); - clone.remove("canUseNode"); - return clone; - }).collect(Collectors.toList()); + jsonObjects = jsonObjects.stream() + .map(jsonObject -> { + JSONObject clone = jsonObject.clone(); + clone.remove("canUseNode"); + return clone; + }) + .collect(Collectors.toList()); return JsonMessage.success("", jsonObjects); } @GetMapping(value = "confirm_fast_install.json", produces = MediaType.APPLICATION_JSON_VALUE) public IJsonMessage> confirmFastInstall(HttpServletRequest request, - @ValidatorItem String id, - @ValidatorItem String ip, - int port) { + @ValidatorItem String id, + @ValidatorItem String ip, + int port) { JSONObject receiveCache = NodeInfoController.getReceiveCache(id); Assert.notNull(receiveCache, "没有对应的缓存信息"); JSONArray jsonArray = receiveCache.getJSONArray("canUseNode"); diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/node/manage/EditProjectController.java b/modules/server/src/main/java/org/dromara/jpom/controller/node/manage/EditProjectController.java deleted file mode 100644 index 47b64a95c..000000000 --- a/modules/server/src/main/java/org/dromara/jpom/controller/node/manage/EditProjectController.java +++ /dev/null @@ -1,123 +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.controller.node.manage; - -import cn.keepbx.jpom.IJsonMessage; -import cn.keepbx.jpom.model.JsonMessage; -import com.alibaba.fastjson2.JSONObject; -import org.dromara.jpom.common.BaseServerController; -import org.dromara.jpom.common.forward.NodeForward; -import org.dromara.jpom.common.forward.NodeUrl; -import org.dromara.jpom.common.validator.ValidatorItem; -import org.dromara.jpom.model.data.NodeModel; -import org.dromara.jpom.permission.ClassFeature; -import org.dromara.jpom.permission.Feature; -import org.dromara.jpom.permission.MethodFeature; -import org.dromara.jpom.permission.NodeDataPermission; -import org.dromara.jpom.service.node.ProjectInfoCacheService; -import org.dromara.jpom.service.system.WhitelistDirectoryService; -import org.springframework.http.MediaType; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.RestController; - -import javax.servlet.http.HttpServletRequest; -import java.util.List; - -/** - * 项目管理 - * - * @author bwcx_jzy - * @since 2018/9/29 - */ -@RestController -@RequestMapping(value = "/node/manage/") -@Feature(cls = ClassFeature.PROJECT) -@NodeDataPermission(cls = ProjectInfoCacheService.class) -public class EditProjectController extends BaseServerController { - - private final ProjectInfoCacheService projectInfoCacheService; - private final WhitelistDirectoryService whitelistDirectoryService; - - public EditProjectController(ProjectInfoCacheService projectInfoCacheService, - WhitelistDirectoryService whitelistDirectoryService) { - this.projectInfoCacheService = projectInfoCacheService; - this.whitelistDirectoryService = whitelistDirectoryService; - } - - @RequestMapping(value = "getProjectData.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public IJsonMessage getProjectData(@ValidatorItem String id) { - JSONObject projectInfo = projectInfoCacheService.getItem(getNode(), id); - return JsonMessage.success("", projectInfo); - } - - /** - * get project access list - * 获取项目的白名单 - * - * @return json - * @author Hotstrip - */ - @RequestMapping(value = "project-access-list", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public IJsonMessage> projectAccessList() { - List jsonArray = whitelistDirectoryService.getProjectDirectory(getNode()); - return JsonMessage.success("success", jsonArray); - } - - /** - * 保存项目 - * - * @param id id - * @return json - */ - @RequestMapping(value = "saveProject", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.EDIT) - public IJsonMessage saveProject(String id, HttpServletRequest httpServletRequest) { - NodeModel node = getNode(); - - // - JsonMessage request = NodeForward.request(node, httpServletRequest, NodeUrl.Manage_SaveProject, "outGivingProject"); - if (request.success()) { - projectInfoCacheService.syncNode(node, id); - } - return request; - } - - - /** - * 释放分发 - * - * @return json - */ - @RequestMapping(value = "release-outgiving", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.EDIT) - public IJsonMessage releaseOutgiving(String id, HttpServletRequest request) { - NodeModel node = getNode(); - - JsonMessage jsonMessage = NodeForward.request(getNode(), request, NodeUrl.Manage_ReleaseOutGiving); - if (jsonMessage.success()) { - projectInfoCacheService.syncNode(node, id); - } - return jsonMessage; - } -} diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/node/manage/ProjectManageControl.java b/modules/server/src/main/java/org/dromara/jpom/controller/node/manage/ProjectManageControl.java index 55d4ff6f3..0ef93e5cd 100644 --- a/modules/server/src/main/java/org/dromara/jpom/controller/node/manage/ProjectManageControl.java +++ b/modules/server/src/main/java/org/dromara/jpom/controller/node/manage/ProjectManageControl.java @@ -55,10 +55,7 @@ import org.dromara.jpom.model.data.NodeModel; import org.dromara.jpom.model.data.RepositoryModel; import org.dromara.jpom.model.enums.BuildReleaseMethod; import org.dromara.jpom.model.node.ProjectInfoCacheModel; -import org.dromara.jpom.permission.ClassFeature; -import org.dromara.jpom.permission.Feature; -import org.dromara.jpom.permission.MethodFeature; -import org.dromara.jpom.permission.NodeDataPermission; +import org.dromara.jpom.permission.*; import org.dromara.jpom.service.dblog.BuildInfoService; import org.dromara.jpom.service.dblog.DbBuildHistoryLogService; import org.dromara.jpom.service.dblog.RepositoryService; @@ -66,6 +63,7 @@ import org.dromara.jpom.service.monitor.MonitorService; import org.dromara.jpom.service.node.ProjectInfoCacheService; import org.dromara.jpom.service.outgiving.LogReadServer; import org.dromara.jpom.service.outgiving.OutGivingServer; +import org.dromara.jpom.service.system.WhitelistDirectoryService; import org.dromara.jpom.system.ServerConfig; import org.springframework.http.MediaType; import org.springframework.util.Assert; @@ -86,6 +84,8 @@ import java.util.stream.Collectors; * 项目管理 * * @author Administrator + * @author bwcx_jzy + * @since 2018/9/29 */ @RestController @RequestMapping(value = "/node/manage/") @@ -102,6 +102,7 @@ public class ProjectManageControl extends BaseServerController { private final ProjectInfoCacheService projectInfoCacheService; private final DbBuildHistoryLogService dbBuildHistoryLogService; private final ServerConfig serverConfig; + private final WhitelistDirectoryService whitelistDirectoryService; public ProjectManageControl(OutGivingServer outGivingServer, LogReadServer logReadServer, @@ -110,7 +111,8 @@ public class ProjectManageControl extends BaseServerController { RepositoryService repositoryService, ProjectInfoCacheService projectInfoCacheService, DbBuildHistoryLogService dbBuildHistoryLogService, - ServerConfig serverConfig) { + ServerConfig serverConfig, + WhitelistDirectoryService whitelistDirectoryService) { this.outGivingServer = outGivingServer; this.logReadServer = logReadServer; this.monitorService = monitorService; @@ -119,6 +121,76 @@ public class ProjectManageControl extends BaseServerController { this.projectInfoCacheService = projectInfoCacheService; this.dbBuildHistoryLogService = dbBuildHistoryLogService; this.serverConfig = serverConfig; + this.whitelistDirectoryService = whitelistDirectoryService; + } + + + private void checkProjectPermission(String id, HttpServletRequest request, NodeModel node) { + if (StrUtil.isEmpty(id)) { + return; + } + String workspaceId = projectInfoCacheService.getCheckUserWorkspace(request); + String fullId = ProjectInfoCacheModel.fullId(workspaceId, node.getId(), id); + boolean exists = projectInfoCacheService.exists(fullId); + Assert.state(exists, "没有对应的数据或者没有此数据权限"); + } + + @RequestMapping(value = "getProjectData.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage getProjectData(@ValidatorItem String id, HttpServletRequest request) { + NodeModel node = getNode(); + this.checkProjectPermission(id, request, node); + JSONObject projectInfo = projectInfoCacheService.getItem(node, id); + return JsonMessage.success("", projectInfo); + } + + /** + * get project access list + * 获取项目的授权 + * + * @return json + * @author Hotstrip + */ + @RequestMapping(value = "project-access-list", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage> projectAccessList() { + List jsonArray = whitelistDirectoryService.getProjectDirectory(getNode()); + return JsonMessage.success("success", jsonArray); + } + + /** + * 保存项目 + * + * @param id id + * @return json + */ + @RequestMapping(value = "saveProject", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EDIT) + public IJsonMessage saveProject(String id, HttpServletRequest request) { + NodeModel node = getNode(); + this.checkProjectPermission(id, request, node); + // + JsonMessage jsonMessage = NodeForward.request(node, request, NodeUrl.Manage_SaveProject, "outGivingProject"); + if (jsonMessage.success()) { + projectInfoCacheService.syncNode(node, id); + } + return jsonMessage; + } + + + /** + * 释放分发 + * + * @return json + */ + @RequestMapping(value = "release-outgiving", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EDIT) + public IJsonMessage releaseOutgiving(String id, HttpServletRequest request) { + NodeModel node = getNode(); + this.checkProjectPermission(id, request, node); + JsonMessage jsonMessage = NodeForward.request(getNode(), request, NodeUrl.Manage_ReleaseOutGiving); + if (jsonMessage.success()) { + projectInfoCacheService.syncNode(node, id); + } + return jsonMessage; } /** @@ -156,7 +228,7 @@ public class ProjectManageControl extends BaseServerController { HttpServletRequest request) { NodeModel nodeModel = getNode(); - + this.checkProjectPermission(id, request, nodeModel); // 检查节点分发 outGivingServer.checkNodeProject(nodeModel.getId(), id, request, "当前项目存在节点分发,不能直接删除"); // 检查日志阅读 @@ -274,6 +346,7 @@ public class ProjectManageControl extends BaseServerController { */ @PostMapping(value = "migrate-workspace", produces = MediaType.APPLICATION_JSON_VALUE) @Feature(method = MethodFeature.EDIT) + @SystemPermission public IJsonMessage migrateWorkspace(@ValidatorItem(value = ValidatorRule.NOT_BLANK) String id, @ValidatorItem(value = ValidatorRule.NOT_BLANK) String toWorkspaceId, @ValidatorItem(value = ValidatorRule.NOT_BLANK) String toNodeId, @@ -350,47 +423,17 @@ public class ProjectManageControl extends BaseServerController { } /** - * 重启项目 + * 操作项目 *

* nodeId,id * * @return json */ - @RequestMapping(value = "restart", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + @RequestMapping(value = "operate", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @Feature(method = MethodFeature.EXECUTE) - public IJsonMessage restart(HttpServletRequest request) { + public IJsonMessage operate(HttpServletRequest request) { NodeModel nodeModel = getNode(); - return NodeForward.request(nodeModel, request, NodeUrl.Manage_Restart); - } - - - /** - * 启动项目 - *

- * nodeId,id - * - * @return json - */ - @RequestMapping(value = "start", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.EXECUTE) - public IJsonMessage start(HttpServletRequest request) { - NodeModel nodeModel = getNode(); - return NodeForward.request(nodeModel, request, NodeUrl.Manage_Start); - } - - - /** - * 关闭项目项目 - *

- * nodeId,id - * - * @return json - */ - @RequestMapping(value = "stop", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.EXECUTE) - public IJsonMessage stop(HttpServletRequest request) { - NodeModel nodeModel = getNode(); - return NodeForward.request(nodeModel, request, NodeUrl.Manage_Stop); + return NodeForward.request(nodeModel, request, NodeUrl.Manage_Operate); } diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/node/manage/file/ProjectFileBackupController.java b/modules/server/src/main/java/org/dromara/jpom/controller/node/manage/file/ProjectFileBackupController.java index 29cada65d..77c1a1373 100644 --- a/modules/server/src/main/java/org/dromara/jpom/controller/node/manage/file/ProjectFileBackupController.java +++ b/modules/server/src/main/java/org/dromara/jpom/controller/node/manage/file/ProjectFileBackupController.java @@ -23,6 +23,7 @@ package org.dromara.jpom.controller.node.manage.file; +import cn.keepbx.jpom.IJsonMessage; import org.dromara.jpom.common.BaseServerController; import org.dromara.jpom.common.forward.NodeForward; import org.dromara.jpom.common.forward.NodeUrl; @@ -57,8 +58,8 @@ public class ProjectFileBackupController extends BaseServerController { * @return list */ @RequestMapping(value = "list-backup", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String listBackup(String id) { - return NodeForward.request(getNode(), getRequest(), NodeUrl.MANAGE_FILE_BACKUP_LIST_BACKUP).toString(); + public IJsonMessage listBackup(String id, HttpServletRequest request) { + return NodeForward.request(getNode(), request, NodeUrl.MANAGE_FILE_BACKUP_LIST_BACKUP); } /** @@ -70,8 +71,8 @@ public class ProjectFileBackupController extends BaseServerController { * @return list */ @RequestMapping(value = "backup-item-files", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String backupItemFiles(String id, String path, @ValidatorItem String backupId) { - return NodeForward.request(getNode(), getRequest(), NodeUrl.MANAGE_FILE_BACKUP_LIST_ITEM_FILES).toString(); + public IJsonMessage backupItemFiles(String id, String path, @ValidatorItem String backupId, HttpServletRequest request) { + return NodeForward.request(getNode(), request, NodeUrl.MANAGE_FILE_BACKUP_LIST_ITEM_FILES); } /** @@ -99,8 +100,8 @@ public class ProjectFileBackupController extends BaseServerController { * @return msg */ @RequestMapping(value = "backup-delete", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String deleteFile(String id, @ValidatorItem String backupId, @ValidatorItem String filename, String levelName, HttpServletRequest request) { - return NodeForward.request(getNode(), request, NodeUrl.MANAGE_FILE_BACKUP_DELETE).toString(); + public IJsonMessage deleteFile(String id, @ValidatorItem String backupId, @ValidatorItem String filename, String levelName, HttpServletRequest request) { + return NodeForward.request(getNode(), request, NodeUrl.MANAGE_FILE_BACKUP_DELETE); } /** @@ -114,7 +115,7 @@ public class ProjectFileBackupController extends BaseServerController { * @return msg */ @RequestMapping(value = "backup-recover", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public String recoverFile(String id, @ValidatorItem String backupId, String type, String filename, String levelName, HttpServletRequest request) { - return NodeForward.request(getNode(), request, NodeUrl.MANAGE_FILE_BACKUP_RECOVER).toString(); + public IJsonMessage recoverFile(String id, @ValidatorItem String backupId, String type, String filename, String levelName, HttpServletRequest request) { + return NodeForward.request(getNode(), request, NodeUrl.MANAGE_FILE_BACKUP_RECOVER); } } diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/node/manage/file/ProjectFileControl.java b/modules/server/src/main/java/org/dromara/jpom/controller/node/manage/file/ProjectFileControl.java index 00a0a5820..14e910e7d 100644 --- a/modules/server/src/main/java/org/dromara/jpom/controller/node/manage/file/ProjectFileControl.java +++ b/modules/server/src/main/java/org/dromara/jpom/controller/node/manage/file/ProjectFileControl.java @@ -27,6 +27,7 @@ import cn.keepbx.jpom.model.JsonMessage; import org.dromara.jpom.common.BaseServerController; import org.dromara.jpom.common.forward.NodeForward; import org.dromara.jpom.common.forward.NodeUrl; +import org.dromara.jpom.common.validator.ValidatorItem; import org.dromara.jpom.controller.outgiving.OutGivingWhitelistService; import org.dromara.jpom.model.data.ServerWhitelist; import org.dromara.jpom.permission.ClassFeature; @@ -65,8 +66,8 @@ public class ProjectFileControl extends BaseServerController { */ @RequestMapping(value = "getFileList", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @Feature(cls = ClassFeature.PROJECT_FILE, method = MethodFeature.LIST) - public String getFileList() { - return NodeForward.request(getNode(), getRequest(), NodeUrl.Manage_File_GetFileList).toString(); + public IJsonMessage getFileList(HttpServletRequest request) { + return NodeForward.request(getNode(), request, NodeUrl.Manage_File_GetFileList); } /** @@ -112,8 +113,8 @@ public class ProjectFileControl extends BaseServerController { */ @RequestMapping(value = "deleteFile", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @Feature(cls = ClassFeature.PROJECT_FILE, method = MethodFeature.DEL) - public String deleteFile(HttpServletRequest request) { - return NodeForward.request(getNode(), request, NodeUrl.Manage_File_DeleteFile).toString(); + public IJsonMessage deleteFile(HttpServletRequest request) { + return NodeForward.request(getNode(), request, NodeUrl.Manage_File_DeleteFile); } @@ -124,8 +125,8 @@ public class ProjectFileControl extends BaseServerController { */ @PostMapping(value = "update_config_file", produces = MediaType.APPLICATION_JSON_VALUE) @Feature(cls = ClassFeature.PROJECT_FILE, method = MethodFeature.EDIT) - public String updateConfigFile(HttpServletRequest request) { - return NodeForward.request(getNode(), request, NodeUrl.Manage_File_UpdateConfigFile).toString(); + public IJsonMessage updateConfigFile(HttpServletRequest request) { + return NodeForward.request(getNode(), request, NodeUrl.Manage_File_UpdateConfigFile); } /** @@ -135,8 +136,8 @@ public class ProjectFileControl extends BaseServerController { */ @GetMapping(value = "read_file", produces = MediaType.APPLICATION_JSON_VALUE) @Feature(cls = ClassFeature.PROJECT_FILE, method = MethodFeature.LIST) - public String readFile(HttpServletRequest request) { - return NodeForward.request(getNode(), request, NodeUrl.Manage_File_ReadFile).toString(); + public IJsonMessage readFile(HttpServletRequest request) { + return NodeForward.request(getNode(), request, NodeUrl.Manage_File_ReadFile); } /** @@ -146,11 +147,11 @@ public class ProjectFileControl extends BaseServerController { */ @GetMapping(value = "remote_download", produces = MediaType.APPLICATION_JSON_VALUE) @Feature(cls = ClassFeature.PROJECT_FILE, method = MethodFeature.REMOTE_DOWNLOAD) - public String remoteDownload(String url, HttpServletRequest request) { + public IJsonMessage remoteDownload(@ValidatorItem String url, HttpServletRequest request) { // 验证远程 地址 ServerWhitelist whitelist = outGivingWhitelistService.getServerWhitelistData(request); whitelist.checkAllowRemoteDownloadHost(url); - return NodeForward.request(getNode(), request, NodeUrl.Manage_File_Remote_Download).toString(); + return NodeForward.request(getNode(), request, NodeUrl.Manage_File_Remote_Download); } /** @@ -160,8 +161,8 @@ public class ProjectFileControl extends BaseServerController { */ @GetMapping(value = "new_file_folder", produces = MediaType.APPLICATION_JSON_VALUE) @Feature(cls = ClassFeature.PROJECT_FILE, method = MethodFeature.EDIT) - public String newFileFolder(HttpServletRequest request) { - return NodeForward.request(getNode(), request, NodeUrl.MANAGE_FILE_NEW_FILE_FOLDER).toString(); + public IJsonMessage newFileFolder(HttpServletRequest request) { + return NodeForward.request(getNode(), request, NodeUrl.MANAGE_FILE_NEW_FILE_FOLDER); } @@ -172,8 +173,8 @@ public class ProjectFileControl extends BaseServerController { */ @GetMapping(value = "rename_file_folder", produces = MediaType.APPLICATION_JSON_VALUE) @Feature(cls = ClassFeature.PROJECT_FILE, method = MethodFeature.EDIT) - public String renameFileFolder(HttpServletRequest request) { - return NodeForward.request(getNode(), request, NodeUrl.MANAGE_FILE_RENAME_FILE_FOLDER).toString(); + public IJsonMessage renameFileFolder(HttpServletRequest request) { + return NodeForward.request(getNode(), request, NodeUrl.MANAGE_FILE_RENAME_FILE_FOLDER); } } diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/node/manage/log/LogBackController.java b/modules/server/src/main/java/org/dromara/jpom/controller/node/manage/log/LogBackController.java index f0be5169c..bb126ddc3 100644 --- a/modules/server/src/main/java/org/dromara/jpom/controller/node/manage/log/LogBackController.java +++ b/modules/server/src/main/java/org/dromara/jpom/controller/node/manage/log/LogBackController.java @@ -60,7 +60,7 @@ public class LogBackController extends BaseServerController { this.projectInfoCacheService = projectInfoCacheService; } - @RequestMapping(value = "export.html", method = RequestMethod.GET) + @RequestMapping(value = "export", method = RequestMethod.GET) @ResponseBody @Feature(method = MethodFeature.DOWNLOAD) public void export(HttpServletRequest request, HttpServletResponse response) { @@ -77,8 +77,8 @@ public class LogBackController extends BaseServerController { @RequestMapping(value = "log-back-list", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @ResponseBody @Feature(method = MethodFeature.LIST) - public IJsonMessage logBackList() { - JSONObject jsonObject = NodeForward.requestData(getNode(), NodeUrl.Manage_Log_logBack, getRequest(), JSONObject.class); + public IJsonMessage logBackList(HttpServletRequest request) { + JSONObject jsonObject = NodeForward.requestData(getNode(), NodeUrl.Manage_Log_logBack, request, JSONObject.class); return JsonMessage.success("success", jsonObject); } @@ -92,8 +92,8 @@ public class LogBackController extends BaseServerController { @RequestMapping(value = "logBack_delete", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @ResponseBody @Feature(method = MethodFeature.DEL) - public String clear(HttpServletRequest request) { - return NodeForward.request(getNode(), request, NodeUrl.Manage_Log_logBack_delete).toString(); + public IJsonMessage clear(HttpServletRequest request) { + return NodeForward.request(getNode(), request, NodeUrl.Manage_Log_logBack_delete); } @RequestMapping(value = "logSize", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @@ -111,7 +111,7 @@ public class LogBackController extends BaseServerController { @RequestMapping(value = "resetLog", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @ResponseBody @Feature(method = MethodFeature.DEL) - public String resetLog() { - return NodeForward.request(getNode(), getRequest(), NodeUrl.Manage_Log_ResetLog).toString(); + public IJsonMessage resetLog(HttpServletRequest request) { + return NodeForward.request(getNode(), request, NodeUrl.Manage_Log_ResetLog); } } diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/node/script/NodeScriptController.java b/modules/server/src/main/java/org/dromara/jpom/controller/node/script/NodeScriptController.java index 3a6973e96..57e9a7200 100644 --- a/modules/server/src/main/java/org/dromara/jpom/controller/node/script/NodeScriptController.java +++ b/modules/server/src/main/java/org/dromara/jpom/controller/node/script/NodeScriptController.java @@ -24,7 +24,6 @@ package org.dromara.jpom.controller.node.script; import cn.hutool.core.io.FileUtil; import cn.hutool.core.util.StrUtil; -import cn.hutool.db.Entity; import cn.keepbx.jpom.IJsonMessage; import cn.keepbx.jpom.model.JsonMessage; import org.dromara.jpom.common.BaseServerController; @@ -37,12 +36,14 @@ import org.dromara.jpom.common.validator.ValidatorItem; import org.dromara.jpom.model.PageResultDto; import org.dromara.jpom.model.data.NodeModel; import org.dromara.jpom.model.node.NodeScriptCacheModel; +import org.dromara.jpom.model.node.ProjectInfoCacheModel; import org.dromara.jpom.model.user.UserModel; import org.dromara.jpom.permission.*; import org.dromara.jpom.service.node.script.NodeScriptExecuteLogServer; import org.dromara.jpom.service.node.script.NodeScriptServer; import org.dromara.jpom.service.user.TriggerTokenLogServer; import org.springframework.http.MediaType; +import org.springframework.util.Assert; import org.springframework.web.bind.annotation.*; import javax.servlet.http.HttpServletRequest; @@ -73,18 +74,6 @@ public class NodeScriptController extends BaseServerController { this.triggerTokenLogServer = triggerTokenLogServer; } - /** - * get script list - * - * @return json - * @author Hotstrip - */ - @RequestMapping(value = "list", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public IJsonMessage> scriptList(HttpServletRequest request) { - PageResultDto pageResultDto = nodeScriptServer.listPageNode(request); - return JsonMessage.success("success", pageResultDto); - } - /** * load node script list * 加载节点脚本列表 @@ -99,10 +88,32 @@ public class NodeScriptController extends BaseServerController { } + private void checkProjectPermission(String id, HttpServletRequest request, NodeModel node) { + if (StrUtil.isEmpty(id)) { + return; + } + String workspaceId = nodeScriptServer.getCheckUserWorkspace(request); + String fullId = ProjectInfoCacheModel.fullId(workspaceId, node.getId(), id); + boolean exists = nodeScriptServer.exists(fullId); + if (!exists) { + // 判断全局脚本 + NodeScriptCacheModel nodeScriptCacheModel = new NodeScriptCacheModel(); + nodeScriptCacheModel.setScriptId(id); + nodeScriptCacheModel.setWorkspaceId(ServerConst.WORKSPACE_GLOBAL); + exists = nodeScriptServer.exists(nodeScriptCacheModel); + if (exists) { + return; + } + } + Assert.state(exists, "没有对应的数据或者没有此数据权限"); + } + @GetMapping(value = "item.json", produces = MediaType.APPLICATION_JSON_VALUE) @Feature(method = MethodFeature.LIST) - public String item(HttpServletRequest request) { - return NodeForward.request(getNode(), request, NodeUrl.Script_Item).toString(); + public IJsonMessage item(HttpServletRequest request, String id) { + NodeModel node = getNode(); + this.checkProjectPermission(id, request, node); + return NodeForward.request(node, request, NodeUrl.Script_Item); } /** @@ -112,8 +123,9 @@ public class NodeScriptController extends BaseServerController { */ @RequestMapping(value = "save.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @Feature(method = MethodFeature.EDIT) - public IJsonMessage save(String autoExecCron, HttpServletRequest request) { + public IJsonMessage save(String id, String autoExecCron, HttpServletRequest request) { NodeModel node = getNode(); + this.checkProjectPermission(id, request, node); this.checkCron(autoExecCron); JsonMessage jsonMessage = NodeForward.request(node, request, NodeUrl.Script_Save, new String[]{}, "nodeId", node.getId()); if (jsonMessage.success()) { @@ -126,6 +138,7 @@ public class NodeScriptController extends BaseServerController { @Feature(method = MethodFeature.DEL) public IJsonMessage del(@ValidatorItem String id, HttpServletRequest request) { NodeModel node = getNode(); + this.checkProjectPermission(id, request, node); JsonMessage requestData = NodeForward.request(node, request, NodeUrl.Script_Del); if (requestData.success()) { nodeScriptServer.syncNode(node); @@ -150,21 +163,6 @@ public class NodeScriptController extends BaseServerController { return JsonMessage.success("主动清除 " + cache + StrUtil.SPACE + msg); } - /** - * 删除节点缓存的所有脚本模版 - * - * @return json - */ - @GetMapping(value = "clear_all", produces = MediaType.APPLICATION_JSON_VALUE) - @SystemPermission(superUser = true) - @Feature(method = MethodFeature.DEL) - public IJsonMessage clearAll() { - Entity where = Entity.create(); - where.set("id", " <> id"); - int del = nodeScriptServer.del(where); - return JsonMessage.success("成功删除" + del + "条脚本模版缓存"); - } - /** * 释放脚本关联的节点 * @@ -200,12 +198,12 @@ public class NodeScriptController extends BaseServerController { } else { updateInfo = item; } - Map map = this.getBuildToken(updateInfo); + Map map = this.getBuildToken(updateInfo, request); return JsonMessage.success(StrUtil.isEmpty(rest) ? "ok" : "重置成功", map); } - private Map getBuildToken(NodeScriptCacheModel item) { - String contextPath = UrlRedirectUtil.getHeaderProxyPath(getRequest(), ServerConst.PROXY_PATH); + private Map getBuildToken(NodeScriptCacheModel item, HttpServletRequest request) { + String contextPath = UrlRedirectUtil.getHeaderProxyPath(request, ServerConst.PROXY_PATH); String url = ServerOpenApi.NODE_SCRIPT_TRIGGER_URL. replace("{id}", item.getId()). replace("{token}", item.getTriggerToken()); diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/node/script/NodeScriptLogController.java b/modules/server/src/main/java/org/dromara/jpom/controller/node/script/NodeScriptLogController.java index a80147db2..71a997935 100644 --- a/modules/server/src/main/java/org/dromara/jpom/controller/node/script/NodeScriptLogController.java +++ b/modules/server/src/main/java/org/dromara/jpom/controller/node/script/NodeScriptLogController.java @@ -66,8 +66,8 @@ public class NodeScriptLogController extends BaseServerController { * @return json */ @RequestMapping(value = "list", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - public IJsonMessage> scriptList() { - PageResultDto pageResultDto = nodeScriptExecuteLogServer.listPageNode(getRequest()); + public IJsonMessage> scriptList(HttpServletRequest request) { + PageResultDto pageResultDto = nodeScriptExecuteLogServer.listPage(request); return JsonMessage.success("", pageResultDto); } @@ -78,9 +78,9 @@ public class NodeScriptLogController extends BaseServerController { */ @RequestMapping(value = "log", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @Feature(method = MethodFeature.LIST) - public String log() { + public IJsonMessage log(HttpServletRequest request) { NodeModel node = getNode(); - return NodeForward.request(node, getRequest(), NodeUrl.SCRIPT_LOG).toString(); + return NodeForward.request(node, request, NodeUrl.SCRIPT_LOG); } /** diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/node/system/WhitelistDirectoryController.java b/modules/server/src/main/java/org/dromara/jpom/controller/node/system/WhitelistDirectoryController.java index 3ed73d9ef..6520eaf79 100644 --- a/modules/server/src/main/java/org/dromara/jpom/controller/node/system/WhitelistDirectoryController.java +++ b/modules/server/src/main/java/org/dromara/jpom/controller/node/system/WhitelistDirectoryController.java @@ -32,6 +32,7 @@ import org.dromara.jpom.model.data.AgentWhitelist; import org.dromara.jpom.model.data.NodeModel; import org.dromara.jpom.permission.ClassFeature; import org.dromara.jpom.permission.Feature; +import org.dromara.jpom.permission.MethodFeature; import org.dromara.jpom.permission.SystemPermission; import org.dromara.jpom.service.system.WhitelistDirectoryService; import org.springframework.http.MediaType; @@ -47,7 +48,7 @@ import java.util.HashMap; import java.util.Map; /** - * 白名单目录 + * 授权目录 * * @author bwcx_jzy * @since 2019/2/28 @@ -55,7 +56,6 @@ import java.util.Map; @RestController @RequestMapping(value = "/node/system") @Feature(cls = ClassFeature.NODE_CONFIG_WHITELIST) -@SystemPermission public class WhitelistDirectoryController extends BaseServerController { private final WhitelistDirectoryService whitelistDirectoryService; @@ -67,13 +67,12 @@ public class WhitelistDirectoryController extends BaseServerController { /** * get whiteList data - * 白名单数据接口 + * 授权数据接口 * * @return json * @author Hotstrip */ @RequestMapping(value = "white-list", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @SystemPermission public IJsonMessage> whiteList(String machineId) { NodeModel nodeModel = tryGetNode(); AgentWhitelist agentWhitelist; @@ -111,8 +110,9 @@ public class WhitelistDirectoryController extends BaseServerController { */ @RequestMapping(value = "whitelistDirectory_submit", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @SystemPermission + @Feature(method = MethodFeature.EDIT) public IJsonMessage whitelistDirectorySubmit(HttpServletRequest request, String machineId) { - JsonMessage objectJsonMessage = this.tryRequestMachine(machineId, request, NodeUrl.WhitelistDirectory_Submit); + JsonMessage objectJsonMessage = this.tryRequestNode(machineId, request, NodeUrl.WhitelistDirectory_Submit); Assert.notNull(objectJsonMessage, "请选择节点"); return objectJsonMessage; } diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/outgiving/LogReadController.java b/modules/server/src/main/java/org/dromara/jpom/controller/outgiving/LogReadController.java index 9eab7f50e..70b5fec9c 100644 --- a/modules/server/src/main/java/org/dromara/jpom/controller/outgiving/LogReadController.java +++ b/modules/server/src/main/java/org/dromara/jpom/controller/outgiving/LogReadController.java @@ -66,8 +66,8 @@ public class LogReadController extends BaseServerController { */ @PostMapping(value = "list", produces = MediaType.APPLICATION_JSON_VALUE) @Feature(method = MethodFeature.LIST) - public IJsonMessage> list() { - PageResultDto pageResultDto = logReadServer.listPage(getRequest()); + public IJsonMessage> list(HttpServletRequest request) { + PageResultDto pageResultDto = logReadServer.listPage(request); return JsonMessage.success("success", pageResultDto); } @@ -79,8 +79,7 @@ public class LogReadController extends BaseServerController { */ @RequestMapping(value = "del.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @Feature(method = MethodFeature.DEL) - public IJsonMessage del(String id) { - HttpServletRequest request = getRequest(); + public IJsonMessage del(String id, HttpServletRequest request) { int byKey = logReadServer.delByKey(id, request); return JsonMessage.success("操作成功"); } @@ -95,7 +94,7 @@ public class LogReadController extends BaseServerController { */ @RequestMapping(value = "save.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @Feature(method = MethodFeature.EDIT) - public IJsonMessage save(@RequestBody JSONObject jsonObject) { + public IJsonMessage save(@RequestBody JSONObject jsonObject, HttpServletRequest request) { Assert.notNull(jsonObject, "请传入参数"); String id = jsonObject.getString("id"); String name = jsonObject.getString("name"); @@ -115,7 +114,6 @@ public class LogReadController extends BaseServerController { if (StrUtil.isEmpty(id)) { logReadServer.insert(logReadModel); } else { - HttpServletRequest request = getRequest(); logReadServer.updateById(logReadModel, request); } return JsonMessage.success("修改成功"); @@ -136,12 +134,12 @@ public class LogReadController extends BaseServerController { */ @RequestMapping(value = "update-cache.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @Feature(method = MethodFeature.EDIT) - public IJsonMessage updateCache(@RequestBody JSONObject jsonObject) { + public IJsonMessage updateCache(@RequestBody JSONObject jsonObject, HttpServletRequest request) { Assert.notNull(jsonObject, "请传入参数"); String id = jsonObject.getString("id"); Assert.hasText(id, "请传入参数"); LogReadModel.CacheDta cacheDta = jsonObject.toJavaObject(LogReadModel.CacheDta.class); - HttpServletRequest request = getRequest(); + LogReadModel logReadModel = new LogReadModel(); logReadModel.setId(id); logReadModel.setCacheData(JSONArray.toJSONString(cacheDta)); diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/outgiving/OutGivingLogController.java b/modules/server/src/main/java/org/dromara/jpom/controller/outgiving/OutGivingLogController.java index 49f604ee5..84bb2afca 100644 --- a/modules/server/src/main/java/org/dromara/jpom/controller/outgiving/OutGivingLogController.java +++ b/modules/server/src/main/java/org/dromara/jpom/controller/outgiving/OutGivingLogController.java @@ -36,6 +36,8 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; +import javax.servlet.http.HttpServletRequest; + /** * 分发日志 * @@ -56,8 +58,8 @@ public class OutGivingLogController extends BaseServerController { @RequestMapping(value = "log_list_data.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @Feature(method = MethodFeature.LIST) - public IJsonMessage> listData() { - PageResultDto pageResult = dbOutGivingLogService.listPage(getRequest()); + public IJsonMessage> listData(HttpServletRequest request) { + PageResultDto pageResult = dbOutGivingLogService.listPage(request); return JsonMessage.success("获取成功", pageResult); } } diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/outgiving/OutGivingProjectController.java b/modules/server/src/main/java/org/dromara/jpom/controller/outgiving/OutGivingProjectController.java index af627730c..87dcc82d2 100644 --- a/modules/server/src/main/java/org/dromara/jpom/controller/outgiving/OutGivingProjectController.java +++ b/modules/server/src/main/java/org/dromara/jpom/controller/outgiving/OutGivingProjectController.java @@ -34,15 +34,24 @@ import cn.keepbx.jpom.model.JsonMessage; import com.alibaba.fastjson2.JSONObject; import lombok.extern.slf4j.Slf4j; import org.dromara.jpom.JpomApplication; +import org.dromara.jpom.build.BuildExtraModule; +import org.dromara.jpom.build.BuildUtil; import org.dromara.jpom.common.BaseServerController; import org.dromara.jpom.common.ServerConst; import org.dromara.jpom.common.validator.ValidatorItem; import org.dromara.jpom.common.validator.ValidatorRule; +import org.dromara.jpom.func.files.model.FileStorageModel; +import org.dromara.jpom.func.files.model.StaticFileStorageModel; +import org.dromara.jpom.func.files.service.FileStorageService; +import org.dromara.jpom.func.files.service.StaticFileStorageService; import org.dromara.jpom.model.AfterOpt; import org.dromara.jpom.model.BaseEnum; import org.dromara.jpom.model.BaseNodeModel; +import org.dromara.jpom.model.EnvironmentMapBuilder; +import org.dromara.jpom.model.data.BuildInfoModel; import org.dromara.jpom.model.data.NodeModel; import org.dromara.jpom.model.data.ServerWhitelist; +import org.dromara.jpom.model.log.BuildHistoryLog; import org.dromara.jpom.model.log.OutGivingLog; import org.dromara.jpom.model.node.ProjectInfoCacheModel; import org.dromara.jpom.model.outgiving.BaseNodeProject; @@ -52,6 +61,8 @@ import org.dromara.jpom.outgiving.OutGivingRun; import org.dromara.jpom.permission.ClassFeature; import org.dromara.jpom.permission.Feature; import org.dromara.jpom.permission.MethodFeature; +import org.dromara.jpom.service.dblog.BuildInfoService; +import org.dromara.jpom.service.dblog.DbBuildHistoryLogService; import org.dromara.jpom.service.node.ProjectInfoCacheService; import org.dromara.jpom.service.outgiving.DbOutGivingLogService; import org.dromara.jpom.service.outgiving.OutGivingServer; @@ -89,17 +100,29 @@ public class OutGivingProjectController extends BaseServerController { private final ServerConfig serverConfig; private final DbOutGivingLogService dbOutGivingLogService; private final ProjectInfoCacheService projectInfoCacheService; + private final BuildInfoService buildInfoService; + private final DbBuildHistoryLogService dbBuildHistoryLogService; + private final FileStorageService fileStorageService; + private final StaticFileStorageService staticFileStorageService; public OutGivingProjectController(OutGivingServer outGivingServer, OutGivingWhitelistService outGivingWhitelistService, ServerConfig serverConfig, DbOutGivingLogService dbOutGivingLogService, - ProjectInfoCacheService projectInfoCacheService) { + ProjectInfoCacheService projectInfoCacheService, + BuildInfoService buildInfoService, + DbBuildHistoryLogService dbBuildHistoryLogService, + FileStorageService fileStorageService, + StaticFileStorageService staticFileStorageService) { this.outGivingServer = outGivingServer; this.outGivingWhitelistService = outGivingWhitelistService; this.serverConfig = serverConfig; this.dbOutGivingLogService = dbOutGivingLogService; this.projectInfoCacheService = projectInfoCacheService; + this.buildInfoService = buildInfoService; + this.dbBuildHistoryLogService = dbBuildHistoryLogService; + this.fileStorageService = fileStorageService; + this.staticFileStorageService = staticFileStorageService; } @RequestMapping(value = "getItemData.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @@ -177,13 +200,14 @@ public class OutGivingProjectController extends BaseServerController { @RequestMapping(value = "upload-sharding", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @Feature(method = MethodFeature.UPLOAD, log = false) public IJsonMessage uploadSharding(String id, - MultipartFile file, - String sliceId, - Integer totalSlice, - Integer nowSlice, - String fileSumMd5) throws IOException { + MultipartFile file, + String sliceId, + Integer totalSlice, + Integer nowSlice, + String fileSumMd5, + HttpServletRequest request) throws IOException { // 状态判断 - this.check(id, (status, outGivingModel1) -> Assert.state(status != OutGivingModel.Status.ING, "当前还在分发中,请等待分发结束")); + this.check(id, (status, outGivingModel1) -> Assert.state(status != OutGivingModel.Status.ING, "当前还在分发中,请等待分发结束"), request); File userTempPath = serverConfig.getUserTempPath(); // 保存文件 this.uploadSharding(file, userTempPath.getAbsolutePath(), sliceId, totalSlice, nowSlice, fileSumMd5); @@ -203,12 +227,12 @@ public class OutGivingProjectController extends BaseServerController { @RequestMapping(value = "upload-sharding-merge", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @Feature(method = MethodFeature.UPLOAD) public IJsonMessage upload(String id, String afterOpt, String clearOld, String autoUnzip, - String secondaryDirectory, String stripComponents, - String selectProject, - String sliceId, - Integer totalSlice, - String fileSumMd5) throws IOException { - this.check(id, (status, outGivingModel1) -> Assert.state(status != OutGivingModel.Status.ING, "当前还在分发中,请等待分发结束")); + String secondaryDirectory, String stripComponents, + String selectProject, + String sliceId, + Integer totalSlice, + String fileSumMd5, HttpServletRequest request) throws IOException { + this.check(id, (status, outGivingModel1) -> Assert.state(status != OutGivingModel.Status.ING, "当前还在分发中,请等待分发结束"), request); AfterOpt afterOpt1 = BaseEnum.getEnum(AfterOpt.class, Convert.toInt(afterOpt, 0)); Assert.notNull(afterOpt1, "请选择分发后的操作"); // @@ -228,7 +252,8 @@ public class OutGivingProjectController extends BaseServerController { outGivingModel.setClearOld(Convert.toBool(clearOld, false)); outGivingModel.setAfterOpt(afterOpt1.getCode()); outGivingModel.setSecondaryDirectory(secondaryDirectory); - + outGivingModel.setMode("upload"); + outGivingModel.setModeData(successFile.getName()); outGivingServer.updateById(outGivingModel); int stripComponentsValue = Convert.toInt(stripComponents, 0); // 开启 @@ -237,13 +262,15 @@ public class OutGivingProjectController extends BaseServerController { .file(dest) .userModel(getUser()) .unzip(unzip) + .mode(outGivingModel.getMode()) + .modeData(outGivingModel.getModeData()) .stripComponents(stripComponentsValue); outGivingRunBuilder.build().startRun(selectProject); return JsonMessage.success("上传成功,开始分发!"); } - private OutGivingModel check(String id, BiConsumer consumer) { - OutGivingModel outGivingModel = outGivingServer.getByKey(id, getRequest()); + private OutGivingModel check(String id, BiConsumer consumer, HttpServletRequest request) { + OutGivingModel outGivingModel = outGivingServer.getByKey(id, request); Assert.notNull(outGivingModel, "上传失败,没有找到对应的分发项目"); // 检查状态 Integer statusCode = outGivingModel.getStatus(); @@ -263,11 +290,13 @@ public class OutGivingProjectController extends BaseServerController { @PostMapping(value = "remote_download", produces = MediaType.APPLICATION_JSON_VALUE) @Feature(method = MethodFeature.REMOTE_DOWNLOAD) public IJsonMessage remoteDownload(String id, String afterOpt, String clearOld, String url, String autoUnzip, - String secondaryDirectory, - String stripComponents, - String selectProject, - HttpServletRequest request) { - OutGivingModel outGivingModel = this.check(id, (status, outGivingModel1) -> Assert.state(status != OutGivingModel.Status.ING, "当前还在分发中,请等待分发结束")); + String secondaryDirectory, + String stripComponents, + String selectProject, + HttpServletRequest request) { + Assert.hasText(url, "填写下载地址"); + Assert.state(StrUtil.length(url) <= 200, "url 长度不能超过 200"); + OutGivingModel outGivingModel = this.check(id, (status, outGivingModel1) -> Assert.state(status != OutGivingModel.Status.ING, "当前还在分发中,请等待分发结束"), request); AfterOpt afterOpt1 = BaseEnum.getEnum(AfterOpt.class, Convert.toInt(afterOpt, 0)); Assert.notNull(afterOpt1, "请选择分发后的操作"); // 验证远程 地址 @@ -278,30 +307,177 @@ public class OutGivingProjectController extends BaseServerController { outGivingModel.setClearOld(Convert.toBool(clearOld, false)); outGivingModel.setAfterOpt(afterOpt1.getCode()); outGivingModel.setSecondaryDirectory(secondaryDirectory); + outGivingModel.setMode("download"); + outGivingModel.setModeData(url); outGivingServer.updateById(outGivingModel); //下载 File file = FileUtil.file(serverConfig.getUserTempPath(), ServerConst.OUTGIVING_FILE, id); FileUtil.mkdir(file); File downloadFile = HttpUtil.downloadFileFromUrl(url, file); + this.startTask(outGivingModel, downloadFile, autoUnzip, stripComponents, selectProject); + return JsonMessage.success("下载成功,开始分发!"); + } + + /** + * 通过构建历史分发 + * + * @param id 分发id + * @param afterOpt 之后的操作 + * @return json + */ + @PostMapping(value = "use-build", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EXECUTE) + public IJsonMessage useBuild(String id, String afterOpt, String clearOld, String buildId, String buildNumberId, + String secondaryDirectory, + String stripComponents, + String selectProject, + HttpServletRequest request) { + + OutGivingModel outGivingModel = this.check(id, (status, outGivingModel1) -> Assert.state(status != OutGivingModel.Status.ING, "当前还在分发中,请等待分发结束"), request); + AfterOpt afterOpt1 = BaseEnum.getEnum(AfterOpt.class, Convert.toInt(afterOpt, 0)); + Assert.notNull(afterOpt1, "请选择分发后的操作"); + + BuildInfoModel infoModel = buildInfoService.getByKey(buildId, request); + Assert.notNull(infoModel, "没有对应的构建"); + BuildHistoryLog buildHistoryLog = new BuildHistoryLog(); + buildHistoryLog.setBuildDataId(infoModel.getId()); + Integer numberId = Convert.toInt(buildNumberId, 0); + buildHistoryLog.setBuildNumberId(numberId); + BuildHistoryLog historyLog = dbBuildHistoryLogService.queryByBean(buildHistoryLog); + Assert.notNull(historyLog, "没有对应的构建记录"); + BuildExtraModule buildExtraModule = BuildExtraModule.build(historyLog); + //String resultDirFileStr = buildExtraModule.getResultDirFile(); + EnvironmentMapBuilder environmentMapBuilder = buildHistoryLog.toEnvironmentMapBuilder(); + boolean tarGz = environmentMapBuilder.getBool(BuildUtil.USE_TAR_GZ, false); + int stripComponentsValue = Convert.toInt(stripComponents, 0); + // + outGivingModel.setClearOld(Convert.toBool(clearOld, false)); + outGivingModel.setAfterOpt(afterOpt1.getCode()); + outGivingModel.setSecondaryDirectory(secondaryDirectory); + outGivingModel.setMode("use-build"); + outGivingModel.setModeData(buildId + ":" + buildNumberId); + File resultDirFile = buildExtraModule.resultDirFile(numberId); + outGivingServer.updateById(outGivingModel); + // + BuildUtil.loadDirPackage(infoModel.getId(), numberId, resultDirFile, tarGz, (unZip, zipFile) -> { + OutGivingRun.OutGivingRunBuilder outGivingRunBuilder = OutGivingRun.builder() + .id(outGivingModel.getId()) + .file(zipFile) + .userModel(getUser()) + .unzip(unZip) + // 由构建配置决定是否删除 + .doneDeleteFile(false) + .mode(outGivingModel.getMode()) + .modeData(outGivingModel.getModeData()) + .stripComponents(stripComponentsValue); + return outGivingRunBuilder.build().startRun(selectProject); + }); + return JsonMessage.success("开始分发!"); + } + + /** + * 文件中心分发文件 + * + * @param id 分发id + * @param afterOpt 之后的操作 + * @return json + */ + @PostMapping(value = "use-file-storage", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EXECUTE) + public IJsonMessage useFileStorage(String id, String afterOpt, String clearOld, String fileId, String autoUnzip, + String secondaryDirectory, + String stripComponents, + String selectProject, + HttpServletRequest request) { + + OutGivingModel outGivingModel = this.check(id, (status, outGivingModel1) -> Assert.state(status != OutGivingModel.Status.ING, "当前还在分发中,请等待分发结束"), request); + AfterOpt afterOpt1 = BaseEnum.getEnum(AfterOpt.class, Convert.toInt(afterOpt, 0)); + Assert.notNull(afterOpt1, "请选择分发后的操作"); + FileStorageModel storageModel = fileStorageService.getByKey(fileId, request); + Assert.notNull(storageModel, "对应的文件不存在"); + // + outGivingModel.setClearOld(Convert.toBool(clearOld, false)); + outGivingModel.setAfterOpt(afterOpt1.getCode()); + outGivingModel.setSecondaryDirectory(secondaryDirectory); + outGivingModel.setMode("file-storage"); + outGivingModel.setModeData(fileId); + outGivingServer.updateById(outGivingModel); + File storageSavePath = serverConfig.fileStorageSavePath(); + File file = FileUtil.file(storageSavePath, storageModel.getPath()); + this.startTask(outGivingModel, file, autoUnzip, stripComponents, selectProject); + return JsonMessage.success("开始分发!"); + } + + /** + * 静态文件分发文件 + * + * @param id 分发id + * @param afterOpt 之后的操作 + * @return json + */ + @PostMapping(value = "use-static-file-storage", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EXECUTE) + public IJsonMessage useStaticFileStorage(String id, String afterOpt, String clearOld, String fileId, String autoUnzip, + String secondaryDirectory, + String stripComponents, + String selectProject, + HttpServletRequest request) { + + OutGivingModel outGivingModel = this.check(id, (status, outGivingModel1) -> Assert.state(status != OutGivingModel.Status.ING, "当前还在分发中,请等待分发结束"), request); + AfterOpt afterOpt1 = BaseEnum.getEnum(AfterOpt.class, Convert.toInt(afterOpt, 0)); + Assert.notNull(afterOpt1, "请选择分发后的操作"); + StaticFileStorageModel storageModel = staticFileStorageService.getByKey(fileId); + String workspaceId = outGivingServer.getCheckUserWorkspace(request); + staticFileStorageService.checkStaticDir(storageModel, workspaceId); + // + outGivingModel.setClearOld(Convert.toBool(clearOld, false)); + outGivingModel.setAfterOpt(afterOpt1.getCode()); + outGivingModel.setSecondaryDirectory(secondaryDirectory); + outGivingModel.setMode("static-file-storage"); + outGivingModel.setModeData(fileId); + outGivingServer.updateById(outGivingModel); + + File file = FileUtil.file(storageModel.getAbsolutePath()); + this.startTask(outGivingModel, file, autoUnzip, stripComponents, selectProject); + return JsonMessage.success("开始分发!"); + } + + /** + * 开始发布任务 + * + * @param outGivingModel 分发对象 + * @param file 文件 + * @param autoUnzip 是否解压 + * @param stripComponents 剔除目录 + * @param selectProject 选择指定项目 + */ + private void startTask(OutGivingModel outGivingModel, File file, String autoUnzip, + String stripComponents, + String selectProject) { + Assert.state(FileUtil.isFile(file), "当前文件丢失不能执行发布任务"); + // boolean unzip = BooleanUtil.toBoolean(autoUnzip); // - this.checkZip(downloadFile, unzip); + this.checkZip(file, unzip); int stripComponentsValue = Convert.toInt(stripComponents, 0); // 开启 OutGivingRun.OutGivingRunBuilder outGivingRunBuilder = OutGivingRun.builder() .id(outGivingModel.getId()) - .file(downloadFile) + .file(file) .userModel(getUser()) .unzip(unzip) + .mode(outGivingModel.getMode()) + .modeData(outGivingModel.getModeData()) + // 可以不再设置-会查询最新的 + // .projectSecondaryDirectory(secondaryDirectory) .stripComponents(stripComponentsValue); outGivingRunBuilder.build().startRun(selectProject); - return JsonMessage.success("下载成功,开始分发!"); } @PostMapping(value = "cancel", produces = MediaType.APPLICATION_JSON_VALUE) @Feature(method = MethodFeature.EXECUTE) - public IJsonMessage cancel(@ValidatorItem String id) { - OutGivingModel outGivingModel = this.check(id, (status, outGivingModel1) -> Assert.state(status == OutGivingModel.Status.ING, "当前状态不是分发中")); + public IJsonMessage cancel(@ValidatorItem String id, HttpServletRequest request) { + OutGivingModel outGivingModel = this.check(id, (status, outGivingModel1) -> Assert.state(status == OutGivingModel.Status.ING, "当前状态不是分发中"), request); OutGivingRun.cancel(outGivingModel.getId(), getUser()); // return JsonMessage.success("取消成功"); @@ -336,9 +512,9 @@ public class OutGivingProjectController extends BaseServerController { @GetMapping(value = "remove-project", produces = MediaType.APPLICATION_JSON_VALUE) @Feature(method = MethodFeature.DEL) public IJsonMessage removeProject(@ValidatorItem String id, - @ValidatorItem String nodeId, - @ValidatorItem String projectId, - HttpServletRequest request) { + @ValidatorItem String nodeId, + @ValidatorItem String projectId, + HttpServletRequest request) { OutGivingModel outGivingModel = outGivingServer.getByKey(id, request); Assert.notNull(outGivingModel, "没有找到对应的分发项目"); List outGivingNodeProjects = outGivingModel.outGivingNodeProjectList(); diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/outgiving/OutGivingProjectEditController.java b/modules/server/src/main/java/org/dromara/jpom/controller/outgiving/OutGivingProjectEditController.java index fa92ee359..5329b10be 100644 --- a/modules/server/src/main/java/org/dromara/jpom/controller/outgiving/OutGivingProjectEditController.java +++ b/modules/server/src/main/java/org/dromara/jpom/controller/outgiving/OutGivingProjectEditController.java @@ -106,15 +106,15 @@ public class OutGivingProjectEditController extends BaseServerController { */ @RequestMapping(value = "save_project", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @Feature(method = MethodFeature.EDIT) - public IJsonMessage save(@ValidatorItem String id, String type) { + public IJsonMessage save(@ValidatorItem String id, String type, HttpServletRequest request) { if ("add".equalsIgnoreCase(type)) { //boolean general = StringUtil.isGeneral(id, 2, 20); //Assert.state(general, "分发id 不能为空并且长度在2-20(英文字母 、数字和下划线)"); String checkId = StrUtil.replace(id, StrUtil.DASHED, StrUtil.UNDERLINE); Validator.validateGeneral(checkId, 2, Const.ID_MAX_LEN, "分发id 不能为空并且长度在2-20(英文字母 、数字和下划线)"); - return addOutGiving(id); + return addOutGiving(id, request); } else { - return updateGiving(id); + return updateGiving(id, request); } } @@ -126,8 +126,7 @@ public class OutGivingProjectEditController extends BaseServerController { */ @RequestMapping(value = "delete_project", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @Feature(method = MethodFeature.DEL) - public IJsonMessage delete(String id, String thorough) { - HttpServletRequest request = getRequest(); + public IJsonMessage delete(String id, String thorough, HttpServletRequest request) { OutGivingModel outGivingModel = outGivingServer.getByKey(id, request); Assert.notNull(outGivingModel, "没有对应的分发项目"); @@ -159,7 +158,8 @@ public class OutGivingProjectEditController extends BaseServerController { return JsonMessage.success("删除成功"); } - private IJsonMessage addOutGiving(String id) { + private IJsonMessage addOutGiving(String id, HttpServletRequest request) { + // 全局判断 id OutGivingModel outGivingModel = outGivingServer.getByKey(id); Assert.isNull(outGivingModel, "分发id已经存在啦"); @@ -167,7 +167,7 @@ public class OutGivingProjectEditController extends BaseServerController { outGivingModel.setOutGivingProject(true); outGivingModel.setId(id); // - List tuples = doData(outGivingModel, false); + List tuples = doData(outGivingModel, false, request); outGivingServer.insert(outGivingModel); IJsonMessage error = saveNodeData(outGivingModel, tuples, false); @@ -175,10 +175,10 @@ public class OutGivingProjectEditController extends BaseServerController { } - private IJsonMessage updateGiving(String id) { - OutGivingModel outGivingModel = outGivingServer.getByKey(id, getRequest()); + private IJsonMessage updateGiving(String id, HttpServletRequest request) { + OutGivingModel outGivingModel = outGivingServer.getByKey(id, request); Assert.notNull(outGivingModel, "没有找到对应的分发id"); - List tuples = doData(outGivingModel, true); + List tuples = doData(outGivingModel, true, request); outGivingServer.updateById(outGivingModel); IJsonMessage error = saveNodeData(outGivingModel, tuples, true); @@ -278,7 +278,7 @@ public class OutGivingProjectEditController extends BaseServerController { * @param edit 是否为编辑模式 * @return String为有异常 */ - private JSONObject getDefData(OutGivingModel outGivingModel, boolean edit) { + private JSONObject getDefData(OutGivingModel outGivingModel, boolean edit, HttpServletRequest request) { JSONObject defData = new JSONObject(); defData.put("id", outGivingModel.getId()); defData.put("name", outGivingModel.getName()); @@ -299,14 +299,14 @@ public class OutGivingProjectEditController extends BaseServerController { defData.put("dslContent", getParameter("dslContent")); } String whitelistDirectory = getParameter("whitelistDirectory"); - ServerWhitelist configDeNewInstance = outGivingWhitelistService.getServerWhitelistData(getRequest()); - List whitelistServerOutGiving = configDeNewInstance.outGiving(); - Assert.state(AgentWhitelist.checkPath(whitelistServerOutGiving, whitelistDirectory), "请选择正确的项目路径,或者还没有配置白名单"); + ServerWhitelist configDeNewInstance = outGivingWhitelistService.getServerWhitelistData(request); + List whitelistServerOutGiving = configDeNewInstance.getOutGiving(); + Assert.state(AgentWhitelist.checkPath(whitelistServerOutGiving, whitelistDirectory), "请选择正确的项目路径,或者还没有配置授权"); defData.put("whitelistDirectory", whitelistDirectory); String logPath = getParameter("logPath"); if (StrUtil.isNotEmpty(logPath)) { - Assert.state(AgentWhitelist.checkPath(whitelistServerOutGiving, logPath), "请选择正确的日志路径,或者还没有配置白名单"); + Assert.state(AgentWhitelist.checkPath(whitelistServerOutGiving, logPath), "请选择正确的日志路径,或者还没有配置授权"); defData.put("logPath", logPath); } String lib = getParameter("lib"); @@ -325,7 +325,7 @@ public class OutGivingProjectEditController extends BaseServerController { * @param outGivingModel 分发实体 * @param edit 是否为编辑模式 */ - private List doData(OutGivingModel outGivingModel, boolean edit) { + private List doData(OutGivingModel outGivingModel, boolean edit, HttpServletRequest request) { outGivingModel.setName(getParameter("name")); outGivingModel.setGroup(getParameter("group")); Assert.hasText(outGivingModel.getName(), "分发名称不能为空"); @@ -336,7 +336,7 @@ public class OutGivingProjectEditController extends BaseServerController { // String nodeIdsStr = getParameter("nodeIds"); List nodeIds = StrUtil.splitTrim(nodeIdsStr, StrUtil.COMMA); - //List nodeModelList = nodeService.listByWorkspace(getRequest()); + //List nodeModelList = nodeService.listByWorkspace(request); Assert.notEmpty(nodeIds, "没有任何节点信息"); // @@ -344,7 +344,7 @@ public class OutGivingProjectEditController extends BaseServerController { AfterOpt afterOpt1 = BaseEnum.getEnum(AfterOpt.class, Convert.toInt(afterOpt, 0)); Assert.notNull(afterOpt1, "请选择分发后的操作"); outGivingModel.setAfterOpt(afterOpt1.getCode()); - JSONObject defData = getDefData(outGivingModel, edit); + JSONObject defData = getDefData(outGivingModel, edit, request); // List outGivingModels = outGivingServer.list(); diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/outgiving/OutGivingWhitelistController.java b/modules/server/src/main/java/org/dromara/jpom/controller/outgiving/OutGivingWhitelistController.java index f49ea7da3..4edee31a4 100644 --- a/modules/server/src/main/java/org/dromara/jpom/controller/outgiving/OutGivingWhitelistController.java +++ b/modules/server/src/main/java/org/dromara/jpom/controller/outgiving/OutGivingWhitelistController.java @@ -24,12 +24,14 @@ package org.dromara.jpom.controller.outgiving; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.RegexPool; +import cn.hutool.core.thread.ThreadUtil; import cn.hutool.core.util.ReUtil; import cn.hutool.core.util.ReflectUtil; import cn.keepbx.jpom.IJsonMessage; import cn.keepbx.jpom.model.JsonMessage; +import lombok.extern.slf4j.Slf4j; import org.dromara.jpom.common.BaseServerController; -import org.dromara.jpom.common.validator.ValidatorItem; +import org.dromara.jpom.func.files.service.StaticFileStorageService; import org.dromara.jpom.model.data.AgentWhitelist; import org.dromara.jpom.model.data.ServerWhitelist; import org.dromara.jpom.permission.ClassFeature; @@ -51,7 +53,7 @@ import java.util.List; import java.util.Map; /** - * 节点白名单 + * 节点授权 * * @author bwcx_jzy * @since 2019/4/22 @@ -59,21 +61,25 @@ import java.util.Map; @RestController @RequestMapping(value = "/outgiving") @Feature(cls = ClassFeature.OUTGIVING_CONFIG_WHITELIST) +@Slf4j public class OutGivingWhitelistController extends BaseServerController { private final SystemParametersServer systemParametersServer; private final OutGivingWhitelistService outGivingWhitelistService; + private final StaticFileStorageService staticFileStorageService; public OutGivingWhitelistController(SystemParametersServer systemParametersServer, - OutGivingWhitelistService outGivingWhitelistService) { + OutGivingWhitelistService outGivingWhitelistService, + StaticFileStorageService staticFileStorageService) { this.systemParametersServer = systemParametersServer; this.outGivingWhitelistService = outGivingWhitelistService; + this.staticFileStorageService = staticFileStorageService; } /** * get whiteList data - * 白名单数据接口 + * 授权数据接口 * * @return json * @author Hotstrip @@ -96,7 +102,7 @@ public class OutGivingWhitelistController extends BaseServerController { /** - * 保存节点白名单 + * 保存节点授权 * * @param outGiving 数据 * @return json @@ -105,36 +111,31 @@ public class OutGivingWhitelistController extends BaseServerController { @SystemPermission @Feature(method = MethodFeature.EDIT) public IJsonMessage whitelistDirectorySubmit(String outGiving, - String allowRemoteDownloadHost, - HttpServletRequest request) { + String allowRemoteDownloadHost, + String staticDir, + HttpServletRequest request) { String workspaceId = nodeService.getCheckUserWorkspace(request); - return this.whitelistDirectorySubmit(outGiving, allowRemoteDownloadHost, workspaceId); + return this.whitelistDirectorySubmit(outGiving, staticDir, allowRemoteDownloadHost, workspaceId); } - /** - * 保存节点白名单 - * - * @param outGiving 数据 - * @return json - */ - @RequestMapping(value = "whitelist-directory-submit2", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @SystemPermission - @Feature(method = MethodFeature.EDIT) - public IJsonMessage whitelistDirectorySubmit2(String outGiving, - String allowRemoteDownloadHost, - @ValidatorItem String workspaceId) { - nodeService.checkUserWorkspace(workspaceId); - return this.whitelistDirectorySubmit(outGiving, allowRemoteDownloadHost, workspaceId); - } private IJsonMessage whitelistDirectorySubmit(String outGiving, - String allowRemoteDownloadHost, - String workspaceId) { - List list = AgentWhitelist.parseToList(outGiving, true, "项目路径白名单不能为空"); - list = AgentWhitelist.covertToArray(list, "项目路径白名单不能位于Jpom目录下"); + String staticDir, + String allowRemoteDownloadHost, + String workspaceId) { + List list = AgentWhitelist.parseToList(outGiving, true, "授权目录不能为空"); + list = AgentWhitelist.covertToArray(list, "授权目录不能位于Jpom目录下"); + String error = AgentWhitelist.findStartsWith(list); + Assert.isNull(error, "授权目录中不能存在包含关系:" + error); + // + List staticDirList = AgentWhitelist.parseToList(staticDir, false, "静态目录授权不能为空"); + staticDirList = AgentWhitelist.covertToArray(staticDirList, 100, "静态目录授权不能位于Jpom目录下"); + error = AgentWhitelist.findStartsWith(staticDirList); + Assert.isNull(error, "静态目录中不能存在包含关系:" + error); ServerWhitelist serverWhitelist = outGivingWhitelistService.getServerWhitelistData(workspaceId); serverWhitelist.setOutGiving(list); + serverWhitelist.setStaticDir(staticDirList); // List allowRemoteDownloadHostList = AgentWhitelist.parseToList(allowRemoteDownloadHost, "运行远程下载的 host 不能配置为空"); // @@ -150,6 +151,15 @@ public class OutGivingWhitelistController extends BaseServerController { systemParametersServer.upsert(id, serverWhitelist, id); String resultData = AgentWhitelist.convertToLine(list); + // 重新检查静态目录任务状态 + ThreadUtil.execute(() -> { + try { + staticFileStorageService.startLoad(); + } catch (Exception e) { + log.error("静态文件任务加载失败", e); + } + }); + return JsonMessage.success("保存成功", resultData); } } diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/script/ScriptController.java b/modules/server/src/main/java/org/dromara/jpom/controller/script/ScriptController.java index b6b1dc925..1f770809f 100644 --- a/modules/server/src/main/java/org/dromara/jpom/controller/script/ScriptController.java +++ b/modules/server/src/main/java/org/dromara/jpom/controller/script/ScriptController.java @@ -96,8 +96,8 @@ public class ScriptController extends BaseServerController { */ @RequestMapping(value = "list", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @Feature(method = MethodFeature.LIST) - public IJsonMessage> scriptList() { - PageResultDto pageResultDto = scriptServer.listPage(getRequest()); + public IJsonMessage> scriptList(HttpServletRequest request) { + PageResultDto pageResultDto = scriptServer.listPage(request); return JsonMessage.success("success", pageResultDto); } @@ -116,13 +116,13 @@ public class ScriptController extends BaseServerController { @RequestMapping(value = "save.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @Feature(method = MethodFeature.EDIT) public IJsonMessage save(String id, - @ValidatorItem String context, - @ValidatorItem String name, - String autoExecCron, - String defArgs, - String description, - String nodeIds, - HttpServletRequest request) { + @ValidatorItem String context, + @ValidatorItem String name, + String autoExecCron, + String defArgs, + String description, + String nodeIds, + HttpServletRequest request) { ScriptModel scriptModel = new ScriptModel(); scriptModel.setId(id); scriptModel.setContext(context); @@ -231,10 +231,11 @@ public class ScriptController extends BaseServerController { }) .collect(Collectors.toList()); // 判断是否可以编辑节点 - boolean prohibitSync = nodeList.stream().anyMatch(jsonObject -> { - String workspaceId11 = (String) jsonObject.get("workspaceId"); - return !StrUtil.equals(workspaceId11, workspaceId); - }); + boolean prohibitSync = nodeList.stream() + .anyMatch(jsonObject -> { + String workspaceId11 = (String) jsonObject.get("workspaceId"); + return !StrUtil.equals(workspaceId11, workspaceId); + }); JSONObject jsonObject = new JSONObject(); jsonObject.put("data", server); jsonObject.put("nodeList", nodeList); @@ -269,8 +270,8 @@ public class ScriptController extends BaseServerController { @GetMapping(value = "sync-to-workspace", produces = MediaType.APPLICATION_JSON_VALUE) @Feature(method = MethodFeature.EDIT) @SystemPermission() - public IJsonMessage syncToWorkspace(@ValidatorItem String ids, @ValidatorItem String toWorkspaceId) { - String nowWorkspaceId = nodeService.getCheckUserWorkspace(getRequest()); + public IJsonMessage syncToWorkspace(@ValidatorItem String ids, @ValidatorItem String toWorkspaceId, HttpServletRequest request) { + String nowWorkspaceId = nodeService.getCheckUserWorkspace(request); // scriptServer.checkUserWorkspace(toWorkspaceId); scriptServer.syncToWorkspace(ids, nowWorkspaceId, toWorkspaceId); @@ -298,12 +299,12 @@ public class ScriptController extends BaseServerController { } else { updateInfo = item; } - Map map = this.getBuildToken(updateInfo); + Map map = this.getBuildToken(updateInfo, request); return JsonMessage.success(StrUtil.isEmpty(rest) ? "ok" : "重置成功", map); } - private Map getBuildToken(ScriptModel item) { - String contextPath = UrlRedirectUtil.getHeaderProxyPath(getRequest(), ServerConst.PROXY_PATH); + private Map getBuildToken(ScriptModel item, HttpServletRequest request) { + String contextPath = UrlRedirectUtil.getHeaderProxyPath(request, ServerConst.PROXY_PATH); String url = ServerOpenApi.SERVER_SCRIPT_TRIGGER_URL. replace("{id}", item.getId()). replace("{token}", item.getTriggerToken()); diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/ssh/CommandInfoController.java b/modules/server/src/main/java/org/dromara/jpom/controller/ssh/CommandInfoController.java index 54f05f90a..ff20e0e0f 100644 --- a/modules/server/src/main/java/org/dromara/jpom/controller/ssh/CommandInfoController.java +++ b/modules/server/src/main/java/org/dromara/jpom/controller/ssh/CommandInfoController.java @@ -43,7 +43,7 @@ import org.dromara.jpom.permission.MethodFeature; import org.dromara.jpom.permission.SystemPermission; import org.dromara.jpom.script.CommandParam; import org.dromara.jpom.service.node.ssh.CommandExecLogService; -import org.dromara.jpom.service.node.ssh.CommandService; +import org.dromara.jpom.service.node.ssh.SshCommandService; import org.dromara.jpom.service.user.TriggerTokenLogServer; import org.dromara.jpom.util.CommandUtil; import org.springframework.http.MediaType; @@ -67,14 +67,14 @@ import java.util.Map; @Feature(cls = ClassFeature.SSH_COMMAND) public class CommandInfoController extends BaseServerController { - private final CommandService commandService; + private final SshCommandService sshCommandService; private final CommandExecLogService commandExecLogService; private final TriggerTokenLogServer triggerTokenLogServer; - public CommandInfoController(CommandService commandService, + public CommandInfoController(SshCommandService sshCommandService, CommandExecLogService commandExecLogService, TriggerTokenLogServer triggerTokenLogServer) { - this.commandService = commandService; + this.sshCommandService = sshCommandService; this.commandExecLogService = commandExecLogService; this.triggerTokenLogServer = triggerTokenLogServer; } @@ -86,8 +86,8 @@ public class CommandInfoController extends BaseServerController { */ @RequestMapping(value = "list", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @Feature(method = MethodFeature.LIST) - public IJsonMessage> page() { - PageResultDto page = commandService.listPage(getRequest()); + public IJsonMessage> page(HttpServletRequest request) { + PageResultDto page = sshCommandService.listPage(request); return JsonMessage.success("", page); } @@ -129,10 +129,10 @@ public class CommandInfoController extends BaseServerController { commandModel.setDefParams(CommandParam.checkStr(defParams)); if (StrUtil.isEmpty(id)) { - commandService.insert(commandModel); + sshCommandService.insert(commandModel); } else { commandModel.setId(id); - commandService.updateById(commandModel, request); + sshCommandService.updateById(commandModel, request); } return JsonMessage.success("操作成功"); } @@ -149,13 +149,13 @@ public class CommandInfoController extends BaseServerController { */ @RequestMapping(value = "del", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @Feature(method = MethodFeature.DEL) - public IJsonMessage del(String id) { + public IJsonMessage del(String id, HttpServletRequest request) { File logFileDir = CommandExecLogModel.logFileDir(id); boolean fastDel = CommandUtil.systemFastDel(logFileDir); Assert.state(!fastDel, "清理日志文件失败"); // - HttpServletRequest request = getRequest(); - commandService.delByKey(id, request); + + sshCommandService.delByKey(id, request); commandExecLogService.delByWorkspace(request, entity -> entity.set("commandId", id)); return JsonMessage.success("操作成功"); } @@ -175,11 +175,11 @@ public class CommandInfoController extends BaseServerController { @RequestMapping(value = "batch", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @Feature(method = MethodFeature.EXECUTE) public IJsonMessage batch(String id, - String params, - @ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "运行节点不能为空") String nodes) throws IOException { + String params, + @ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "运行节点不能为空") String nodes) throws IOException { Assert.hasText(id, "请选择执行的命令"); Assert.hasText(nodes, "请选择执行节点"); - String batchId = commandService.executeBatch(id, params, nodes); + String batchId = sshCommandService.executeBatch(id, params, nodes); return JsonMessage.success("操作成功", batchId); } @@ -193,11 +193,11 @@ public class CommandInfoController extends BaseServerController { @GetMapping(value = "sync-to-workspace", produces = MediaType.APPLICATION_JSON_VALUE) @Feature(method = MethodFeature.EDIT) @SystemPermission() - public IJsonMessage syncToWorkspace(@ValidatorItem String ids, @ValidatorItem String toWorkspaceId) { - String nowWorkspaceId = nodeService.getCheckUserWorkspace(getRequest()); + public IJsonMessage syncToWorkspace(@ValidatorItem String ids, @ValidatorItem String toWorkspaceId, HttpServletRequest request) { + String nowWorkspaceId = nodeService.getCheckUserWorkspace(request); // - commandService.checkUserWorkspace(toWorkspaceId); - commandService.syncToWorkspace(ids, nowWorkspaceId, toWorkspaceId); + sshCommandService.checkUserWorkspace(toWorkspaceId); + sshCommandService.syncToWorkspace(ids, nowWorkspaceId, toWorkspaceId); return JsonMessage.success("操作成功"); } @@ -209,25 +209,25 @@ public class CommandInfoController extends BaseServerController { */ @RequestMapping(value = "trigger-url", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @Feature(method = MethodFeature.EDIT) - public IJsonMessage> getTriggerUrl(String id, String rest) { - CommandModel item = commandService.getByKey(id, getRequest()); + public IJsonMessage> getTriggerUrl(String id, String rest, HttpServletRequest request) { + CommandModel item = sshCommandService.getByKey(id, request); UserModel user = getUser(); CommandModel updateInfo; if (StrUtil.isEmpty(item.getTriggerToken()) || StrUtil.isNotEmpty(rest)) { updateInfo = new CommandModel(); updateInfo.setId(id); - updateInfo.setTriggerToken(triggerTokenLogServer.restToken(item.getTriggerToken(), commandService.typeName(), + updateInfo.setTriggerToken(triggerTokenLogServer.restToken(item.getTriggerToken(), sshCommandService.typeName(), item.getId(), user.getId())); - commandService.updateById(updateInfo); + sshCommandService.updateById(updateInfo); } else { updateInfo = item; } - Map map = this.getBuildToken(updateInfo); + Map map = this.getBuildToken(updateInfo, request); return JsonMessage.success(StrUtil.isEmpty(rest) ? "ok" : "重置成功", map); } - private Map getBuildToken(CommandModel item) { - String contextPath = UrlRedirectUtil.getHeaderProxyPath(getRequest(), ServerConst.PROXY_PATH); + private Map getBuildToken(CommandModel item, HttpServletRequest request) { + String contextPath = UrlRedirectUtil.getHeaderProxyPath(request, ServerConst.PROXY_PATH); String url = ServerOpenApi.SSH_COMMAND_TRIGGER_URL. replace("{id}", item.getId()). replace("{token}", item.getTriggerToken()); diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/ssh/CommandLogController.java b/modules/server/src/main/java/org/dromara/jpom/controller/ssh/CommandLogController.java index ecb1fc9da..f2097b7af 100644 --- a/modules/server/src/main/java/org/dromara/jpom/controller/ssh/CommandLogController.java +++ b/modules/server/src/main/java/org/dromara/jpom/controller/ssh/CommandLogController.java @@ -42,6 +42,7 @@ import org.springframework.http.MediaType; import org.springframework.util.Assert; import org.springframework.web.bind.annotation.*; +import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.File; import java.util.List; @@ -70,8 +71,8 @@ public class CommandLogController extends BaseServerController { */ @RequestMapping(value = "list", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @Feature(method = MethodFeature.LIST) - public IJsonMessage> page() { - PageResultDto page = commandExecLogService.listPage(getRequest()); + public IJsonMessage> page(HttpServletRequest request) { + PageResultDto page = commandExecLogService.listPage(request); return JsonMessage.success("", page); } @@ -87,8 +88,8 @@ public class CommandLogController extends BaseServerController { */ @RequestMapping(value = "del", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @Feature(method = MethodFeature.DEL) - public IJsonMessage del(String id) { - CommandExecLogModel execLogModel = commandExecLogService.getByKey(id); + public IJsonMessage del(String id, HttpServletRequest request) { + CommandExecLogModel execLogModel = commandExecLogService.getByKey(id, request); Assert.notNull(execLogModel, "没有对应的记录"); File logFile = execLogModel.logFile(); boolean fastDel = CommandUtil.systemFastDel(logFile); @@ -122,8 +123,10 @@ public class CommandLogController extends BaseServerController { */ @GetMapping(value = "batch_list", produces = MediaType.APPLICATION_JSON_VALUE) @Feature(method = MethodFeature.LIST) - public IJsonMessage> batchList(@ValidatorItem String commandId, @ValidatorItem String batchId) { + public IJsonMessage> batchList(@ValidatorItem String commandId, @ValidatorItem String batchId, HttpServletRequest request) { CommandExecLogModel commandExecLogModel = new CommandExecLogModel(); + String workspace = commandExecLogService.getCheckUserWorkspace(request); + commandExecLogModel.setWorkspaceId(workspace); commandExecLogModel.setCommandId(commandId); commandExecLogModel.setBatchId(batchId); List commandExecLogModels = commandExecLogService.listByBean(commandExecLogModel); @@ -147,8 +150,8 @@ public class CommandLogController extends BaseServerController { @RequestMapping(value = "log", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @Feature(method = MethodFeature.LIST) public IJsonMessage log(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "没有数据") String id, - @ValidatorItem(value = ValidatorRule.POSITIVE_INTEGER, msg = "line") int line) { - CommandExecLogModel item = commandExecLogService.getByKey(id, getRequest()); + @ValidatorItem(value = ValidatorRule.POSITIVE_INTEGER, msg = "line") int line, HttpServletRequest request) { + CommandExecLogModel item = commandExecLogService.getByKey(id, request); Assert.notNull(item, "没有对应数据"); File file = item.logFile(); @@ -178,8 +181,8 @@ public class CommandLogController extends BaseServerController { @RequestMapping(value = "download_log", method = RequestMethod.GET) @ResponseBody @Feature(method = MethodFeature.DOWNLOAD) - public void downloadLog(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "没有数据") String logId, HttpServletResponse response) { - CommandExecLogModel item = commandExecLogService.getByKey(logId); + public void downloadLog(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "没有数据") String logId, HttpServletRequest request, HttpServletResponse response) { + CommandExecLogModel item = commandExecLogService.getByKey(logId, request); Assert.notNull(item, "没有对应数据"); File logFile = item.logFile(); if (!FileUtil.exist(logFile)) { diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/system/LogManageController.java b/modules/server/src/main/java/org/dromara/jpom/controller/system/LogManageController.java index a34887cb9..619bc930b 100644 --- a/modules/server/src/main/java/org/dromara/jpom/controller/system/LogManageController.java +++ b/modules/server/src/main/java/org/dromara/jpom/controller/system/LogManageController.java @@ -86,9 +86,9 @@ public class LogManageController extends BaseServerController { @RequestMapping(value = "log_del.json", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @Feature(method = MethodFeature.DEL) public IJsonMessage logData(String nodeId, - @ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "path错误") String path) { + @ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "path错误") String path, HttpServletRequest request) { if (StrUtil.isNotEmpty(nodeId)) { - return NodeForward.request(getNode(), getRequest(), NodeUrl.DelSystemLog); + return NodeForward.request(getNode(), request, NodeUrl.DelSystemLog); } File file = FileUtil.file(LogbackConfig.getPath(), path); // 判断修改时间 diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/system/SystemConfigController.java b/modules/server/src/main/java/org/dromara/jpom/controller/system/SystemConfigController.java index dcacbbda6..3b76389ca 100644 --- a/modules/server/src/main/java/org/dromara/jpom/controller/system/SystemConfigController.java +++ b/modules/server/src/main/java/org/dromara/jpom/controller/system/SystemConfigController.java @@ -186,7 +186,7 @@ public class SystemConfigController extends BaseServerController { /** - * 加载服务端的 ip 白名单配置 + * 加载服务端的 ip 授权配置 * * @return json */ diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/system/WorkspaceController.java b/modules/server/src/main/java/org/dromara/jpom/controller/system/WorkspaceController.java index 5fc28cfab..18a5c3ad9 100644 --- a/modules/server/src/main/java/org/dromara/jpom/controller/system/WorkspaceController.java +++ b/modules/server/src/main/java/org/dromara/jpom/controller/system/WorkspaceController.java @@ -56,6 +56,7 @@ import org.springframework.http.MediaType; import org.springframework.util.Assert; import org.springframework.web.bind.annotation.*; +import javax.servlet.http.HttpServletRequest; import java.io.InputStream; import java.util.ArrayList; import java.util.List; @@ -138,8 +139,8 @@ public class WorkspaceController extends BaseServerController { */ @PostMapping(value = "/list", produces = MediaType.APPLICATION_JSON_VALUE) @Feature(method = MethodFeature.LIST) - public IJsonMessage> list() { - PageResultDto listPage = workspaceService.listPage(getRequest()); + public IJsonMessage> list(HttpServletRequest request) { + PageResultDto listPage = workspaceService.listPage(request); return JsonMessage.success("", listPage); } @@ -291,7 +292,6 @@ public class WorkspaceController extends BaseServerController { //"classpath:/menus/index.json" //"classpath:/menus/node-index.json" config.put("serverMenus", this.readMenusJson("classpath:/menus/index.json")); - config.put("nodeMenus", this.readMenusJson("classpath:/menus/node-index.json")); return JsonMessage.success("", config); } diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/system/WorkspaceEnvVarController.java b/modules/server/src/main/java/org/dromara/jpom/controller/system/WorkspaceEnvVarController.java index 16e103a05..0ef503a22 100644 --- a/modules/server/src/main/java/org/dromara/jpom/controller/system/WorkspaceEnvVarController.java +++ b/modules/server/src/main/java/org/dromara/jpom/controller/system/WorkspaceEnvVarController.java @@ -265,7 +265,7 @@ public class WorkspaceEnvVarController extends BaseServerController { */ @RequestMapping(value = "trigger-url", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @Feature(method = MethodFeature.EDIT) - public IJsonMessage> getTriggerUrl(@ValidatorItem String id, @ValidatorItem String workspaceId, String rest) { + public IJsonMessage> getTriggerUrl(@ValidatorItem String id, @ValidatorItem String workspaceId, String rest, HttpServletRequest request) { workspaceEnvVarService.checkUserWorkspace(workspaceId); WorkspaceEnvVarModel item = workspaceEnvVarService.getByKey(id); Assert.notNull(item, "没有对应的环境变量"); @@ -283,12 +283,12 @@ public class WorkspaceEnvVarController extends BaseServerController { } else { updateInfo = item; } - Map map = this.getBuildToken(updateInfo); + Map map = this.getBuildToken(updateInfo, request); return JsonMessage.success(StrUtil.isEmpty(rest) ? "ok" : "重置成功", map); } - private Map getBuildToken(WorkspaceEnvVarModel item) { - String contextPath = UrlRedirectUtil.getHeaderProxyPath(getRequest(), ServerConst.PROXY_PATH); + private Map getBuildToken(WorkspaceEnvVarModel item, HttpServletRequest request) { + String contextPath = UrlRedirectUtil.getHeaderProxyPath(request, ServerConst.PROXY_PATH); String url = ServerOpenApi.SERVER_ENV_VAR_TRIGGER_URL. replace("{id}", item.getId()). replace("{token}", item.getTriggerToken()); diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/user/UserBasicInfoController.java b/modules/server/src/main/java/org/dromara/jpom/controller/user/UserBasicInfoController.java index b2bbf0dc5..bea9bb8df 100644 --- a/modules/server/src/main/java/org/dromara/jpom/controller/user/UserBasicInfoController.java +++ b/modules/server/src/main/java/org/dromara/jpom/controller/user/UserBasicInfoController.java @@ -30,6 +30,9 @@ import cn.hutool.core.lang.Validator; import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.RandomUtil; import cn.hutool.core.util.StrUtil; +import cn.hutool.db.Entity; +import cn.hutool.db.sql.Direction; +import cn.hutool.db.sql.Order; import cn.keepbx.jpom.IJsonMessage; import cn.keepbx.jpom.model.JsonMessage; import com.alibaba.fastjson2.JSONObject; @@ -47,11 +50,11 @@ import org.dromara.jpom.func.user.server.UserLoginLogServer; import org.dromara.jpom.model.PageResultDto; import org.dromara.jpom.model.data.MailAccountModel; import org.dromara.jpom.model.data.WorkspaceModel; +import org.dromara.jpom.model.log.BuildHistoryLog; import org.dromara.jpom.model.log.UserOperateLogV1; import org.dromara.jpom.model.user.UserModel; import org.dromara.jpom.monitor.EmailUtil; -import org.dromara.jpom.permission.Feature; -import org.dromara.jpom.permission.MethodFeature; +import org.dromara.jpom.service.dblog.DbBuildHistoryLogService; import org.dromara.jpom.service.dblog.DbUserOperateLogService; import org.dromara.jpom.service.system.SystemParametersServer; import org.dromara.jpom.service.user.UserBindWorkspaceService; @@ -88,6 +91,7 @@ public class UserBasicInfoController extends BaseServerController { private final UserLoginLogServer userLoginLogServer; private final DbUserOperateLogService dbUserOperateLogService; private final ClusterInfoService clusterInfoService; + private final DbBuildHistoryLogService dbBuildHistoryLogService; public UserBasicInfoController(SystemParametersServer systemParametersServer, UserBindWorkspaceService userBindWorkspaceService, @@ -95,7 +99,8 @@ public class UserBasicInfoController extends BaseServerController { ServerConfig serverConfig, UserLoginLogServer userLoginLogServer, DbUserOperateLogService dbUserOperateLogService, - ClusterInfoService clusterInfoService) { + ClusterInfoService clusterInfoService, + DbBuildHistoryLogService dbBuildHistoryLogService) { this.systemParametersServer = systemParametersServer; this.userBindWorkspaceService = userBindWorkspaceService; this.userService = userService; @@ -103,6 +108,7 @@ public class UserBasicInfoController extends BaseServerController { this.userLoginLogServer = userLoginLogServer; this.dbUserOperateLogService = dbUserOperateLogService; this.clusterInfoService = clusterInfoService; + this.dbBuildHistoryLogService = dbBuildHistoryLogService; } @@ -122,6 +128,8 @@ public class UserBasicInfoController extends BaseServerController { map.put("id", userModel.getId()); map.put("name", userModel.getName()); map.put("systemUser", userModel.isSystemUser()); + map.put("superSystemUser", userModel.isSuperSystemUser()); + map.put("demoUser", userModel.isDemoUser()); map.put("email", userModel.getEmail()); map.put("dingDing", userModel.getDingDing()); map.put("workWx", userModel.getWorkWx()); @@ -295,7 +303,6 @@ public class UserBasicInfoController extends BaseServerController { * @return json */ @RequestMapping(value = "list-login-log-data", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.LIST) public IJsonMessage> listLoginLogData(HttpServletRequest request) { UserModel user = getUser(); PageResultDto pageResult = userLoginLogServer.listPageByUserId(request, user.getId()); @@ -308,13 +315,40 @@ public class UserBasicInfoController extends BaseServerController { * @return json */ @RequestMapping(value = "list-operate-log-data", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) - @Feature(method = MethodFeature.LIST) public IJsonMessage> listOperateLogData(HttpServletRequest request) { UserModel user = getUser(); PageResultDto pageResult = dbUserOperateLogService.listPageByUserId(request, user.getId()); return JsonMessage.success("", pageResult); } + @RequestMapping(value = "recent-log-data", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) + public IJsonMessage recentData(HttpServletRequest request) { + UserModel user = getUser(); + JSONObject jsonObject = new JSONObject(); + { + Entity entity = Entity.create(); + entity.set("userId", user.getId()); + List operateLog = dbUserOperateLogService.queryList(entity, 10, new Order("createTimeMillis", Direction.DESC)); + jsonObject.put("operateLog", operateLog); + } + { + Entity entity = Entity.create(); + entity.set("modifyUser", user.getId()); + List loginLog = userLoginLogServer.queryList(entity, 10, new Order("createTimeMillis", Direction.DESC)); + + jsonObject.put("loginLog", loginLog); + } + { + String workspaceId = dbBuildHistoryLogService.getCheckUserWorkspace(request); + Entity entity = Entity.create(); + entity.set("workspaceId", workspaceId); + entity.set("modifyUser", user.getId()); + List loginLog = dbBuildHistoryLogService.queryList(entity, 10, new Order("createTimeMillis", Direction.DESC)); + jsonObject.put("buildLog", loginLog); + } + return JsonMessage.success("", jsonObject); + } + /** * 查询集群列表 * diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/user/UserListController.java b/modules/server/src/main/java/org/dromara/jpom/controller/user/UserListController.java index cedc44323..5a7bb576e 100644 --- a/modules/server/src/main/java/org/dromara/jpom/controller/user/UserListController.java +++ b/modules/server/src/main/java/org/dromara/jpom/controller/user/UserListController.java @@ -39,12 +39,14 @@ import org.dromara.jpom.permission.ClassFeature; import org.dromara.jpom.permission.Feature; import org.dromara.jpom.permission.MethodFeature; import org.dromara.jpom.permission.SystemPermission; +import org.dromara.jpom.service.user.TriggerTokenLogServer; import org.dromara.jpom.service.user.UserBindWorkspaceService; import org.dromara.jpom.service.user.UserService; import org.springframework.http.MediaType; import org.springframework.util.Assert; import org.springframework.web.bind.annotation.*; +import javax.servlet.http.HttpServletRequest; import java.util.List; /** @@ -60,11 +62,14 @@ public class UserListController extends BaseServerController { private final UserService userService; private final UserBindWorkspaceService userBindWorkspaceService; + private final TriggerTokenLogServer triggerTokenLogServer; public UserListController(UserService userService, - UserBindWorkspaceService userBindWorkspaceService) { + UserBindWorkspaceService userBindWorkspaceService, + TriggerTokenLogServer triggerTokenLogServer) { this.userService = userService; this.userBindWorkspaceService = userBindWorkspaceService; + this.triggerTokenLogServer = triggerTokenLogServer; } /** @@ -74,8 +79,8 @@ public class UserListController extends BaseServerController { */ @RequestMapping(value = "get_user_list", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @Feature(method = MethodFeature.LIST) - public IJsonMessage> getUserList() { - PageResultDto userModelPageResultDto = userService.listPage(getRequest()); + public IJsonMessage> getUserList(HttpServletRequest request) { + PageResultDto userModelPageResultDto = userService.listPage(request); userModelPageResultDto.each(userModel -> { boolean bindMfa = userService.hasBindMfa(userModel.getId()); if (bindMfa) { @@ -217,6 +222,8 @@ public class UserListController extends BaseServerController { userService.delByKey(id); // 删除工作空间 userBindWorkspaceService.deleteByUserId(id); + // + triggerTokenLogServer.delByUserId(id); return JsonMessage.success("删除成功"); } diff --git a/modules/server/src/main/java/org/dromara/jpom/controller/user/UserPermissionGroupController.java b/modules/server/src/main/java/org/dromara/jpom/controller/user/UserPermissionGroupController.java index 0e9ef0e5a..d8d81aec5 100644 --- a/modules/server/src/main/java/org/dromara/jpom/controller/user/UserPermissionGroupController.java +++ b/modules/server/src/main/java/org/dromara/jpom/controller/user/UserPermissionGroupController.java @@ -48,6 +48,7 @@ import org.springframework.http.MediaType; import org.springframework.util.Assert; import org.springframework.web.bind.annotation.*; +import javax.servlet.http.HttpServletRequest; import java.util.List; import java.util.Objects; import java.util.stream.Collectors; @@ -81,8 +82,8 @@ public class UserPermissionGroupController extends BaseServerController { */ @RequestMapping(value = "get-list", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @Feature(method = MethodFeature.LIST) - public IJsonMessage> getUserList() { - PageResultDto userModelPageResultDto = userPermissionGroupServer.listPage(getRequest()); + public IJsonMessage> getUserList(HttpServletRequest request) { + PageResultDto userModelPageResultDto = userPermissionGroupServer.listPage(request); return new JsonMessage<>(200, "", userModelPageResultDto); } @@ -106,11 +107,11 @@ public class UserPermissionGroupController extends BaseServerController { @PostMapping(value = "edit", produces = MediaType.APPLICATION_JSON_VALUE) @Feature(method = MethodFeature.EDIT) public IJsonMessage edit(String id, - @ValidatorItem String name, - String description, - String prohibitExecute, - String allowExecute, - @ValidatorItem String workspace) { + @ValidatorItem String name, + String description, + String prohibitExecute, + String allowExecute, + @ValidatorItem String workspace) { UserPermissionGroupBean userPermissionGroupBean = new UserPermissionGroupBean(); userPermissionGroupBean.setName(name); userPermissionGroupBean.setDescription(description); diff --git a/modules/server/src/main/java/org/dromara/jpom/system/AuthorizeException.java b/modules/server/src/main/java/org/dromara/jpom/exception/AgentAuthorizeException.java similarity index 89% rename from modules/server/src/main/java/org/dromara/jpom/system/AuthorizeException.java rename to modules/server/src/main/java/org/dromara/jpom/exception/AgentAuthorizeException.java index 4f66f4cc6..dd908e882 100644 --- a/modules/server/src/main/java/org/dromara/jpom/system/AuthorizeException.java +++ b/modules/server/src/main/java/org/dromara/jpom/exception/AgentAuthorizeException.java @@ -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.exception; import cn.keepbx.jpom.IJsonMessage; import cn.keepbx.jpom.model.JsonMessage; @@ -31,10 +31,10 @@ import cn.keepbx.jpom.model.JsonMessage; * @author bwcx_jzy * @since 2019/4/17 */ -public class AuthorizeException extends RuntimeException { +public class AgentAuthorizeException extends RuntimeException { private final JsonMessage jsonMessage; - public AuthorizeException(JsonMessage jsonMessage) { + public AgentAuthorizeException(JsonMessage jsonMessage) { super(jsonMessage.getMsg()); this.jsonMessage = jsonMessage; } diff --git a/modules/server/src/main/java/org/dromara/jpom/system/AgentException.java b/modules/server/src/main/java/org/dromara/jpom/exception/AgentException.java similarity index 97% rename from modules/server/src/main/java/org/dromara/jpom/system/AgentException.java rename to modules/server/src/main/java/org/dromara/jpom/exception/AgentException.java index 59a20d74f..3c445da79 100644 --- a/modules/server/src/main/java/org/dromara/jpom/system/AgentException.java +++ b/modules/server/src/main/java/org/dromara/jpom/exception/AgentException.java @@ -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.exception; /** * agent 插件端异常 diff --git a/modules/server/src/main/java/org/dromara/jpom/exception/PermissionException.java b/modules/server/src/main/java/org/dromara/jpom/exception/PermissionException.java new file mode 100644 index 000000000..bfe27b640 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/exception/PermissionException.java @@ -0,0 +1,35 @@ +/* + * 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 23/12/29 029 + */ +public class PermissionException extends RuntimeException { + public PermissionException(String message) { + super(message); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/func/assets/controller/BaseSshFileController.java b/modules/server/src/main/java/org/dromara/jpom/func/assets/controller/BaseSshFileController.java index 09c394f40..ebb3900f1 100644 --- a/modules/server/src/main/java/org/dromara/jpom/func/assets/controller/BaseSshFileController.java +++ b/modules/server/src/main/java/org/dromara/jpom/func/assets/controller/BaseSshFileController.java @@ -118,15 +118,15 @@ public abstract class BaseSshFileController extends BaseServerController { * 验证数据id 和目录合法性 * * @param id 数据id - * @param allowPathParent 想要验证的目录 (白名单) - * @param nextPath 白名单后的二级路径 + * @param allowPathParent 想要验证的目录 (授权) + * @param nextPath 授权后的二级路径 * @param function 回调 * @param 泛型 * @return 处理后的数据 */ protected abstract T checkConfigPathChildren(String id, String allowPathParent, String nextPath, BiFunction function); - @RequestMapping(value = "download.html", method = RequestMethod.GET) + @RequestMapping(value = "download", method = RequestMethod.GET) @Feature(method = MethodFeature.DOWNLOAD) public void download(@ValidatorItem String id, @ValidatorItem String allowPathParent, diff --git a/modules/server/src/main/java/org/dromara/jpom/func/assets/controller/MachineNodeController.java b/modules/server/src/main/java/org/dromara/jpom/func/assets/controller/MachineNodeController.java index ef7f41a11..722e0504f 100644 --- a/modules/server/src/main/java/org/dromara/jpom/func/assets/controller/MachineNodeController.java +++ b/modules/server/src/main/java/org/dromara/jpom/func/assets/controller/MachineNodeController.java @@ -35,10 +35,15 @@ import org.dromara.jpom.func.assets.server.MachineNodeServer; import org.dromara.jpom.model.PageResultDto; import org.dromara.jpom.model.data.NodeModel; import org.dromara.jpom.model.data.WorkspaceModel; +import org.dromara.jpom.model.node.NodeScriptCacheModel; +import org.dromara.jpom.model.node.ProjectInfoCacheModel; import org.dromara.jpom.permission.ClassFeature; import org.dromara.jpom.permission.Feature; import org.dromara.jpom.permission.MethodFeature; import org.dromara.jpom.permission.SystemPermission; +import org.dromara.jpom.service.node.NodeService; +import org.dromara.jpom.service.node.ProjectInfoCacheService; +import org.dromara.jpom.service.node.script.NodeScriptServer; import org.dromara.jpom.service.system.WorkspaceService; import org.springframework.http.MediaType; import org.springframework.util.Assert; @@ -62,11 +67,20 @@ import java.util.Optional; public class MachineNodeController extends BaseGroupNameController { private final WorkspaceService workspaceService; + private final ProjectInfoCacheService projectInfoCacheService; + private final NodeScriptServer nodeScriptServer; + private final NodeService nodeService; public MachineNodeController(WorkspaceService workspaceService, - MachineNodeServer machineNodeServer) { + MachineNodeServer machineNodeServer, + ProjectInfoCacheService projectInfoCacheService, + NodeScriptServer nodeScriptServer, + NodeService nodeService) { super(machineNodeServer); this.workspaceService = workspaceService; + this.projectInfoCacheService = projectInfoCacheService; + this.nodeScriptServer = nodeScriptServer; + this.nodeService = nodeService; } @PostMapping(value = "list-data", produces = MediaType.APPLICATION_JSON_VALUE) @@ -150,21 +164,21 @@ public class MachineNodeController extends BaseGroupNameController { /** - * 保存白名单配置 + * 保存授权配置 * * @return json */ @RequestMapping(value = "save-whitelist", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE) @Feature(cls = ClassFeature.SYSTEM_NODE_WHITELIST, method = MethodFeature.EDIT) public IJsonMessage saveWhitelist(@ValidatorItem(msg = "请选择分发的机器") String ids, - HttpServletRequest request) { + HttpServletRequest request) { // List idList = StrUtil.splitTrim(ids, StrUtil.COMMA); for (String s : idList) { MachineNodeModel machineNodeModel = machineNodeServer.getByKey(s); Assert.notNull(machineNodeModel, "没有对应的机器"); JsonMessage jsonMessage = NodeForward.request(machineNodeModel, request, NodeUrl.WhitelistDirectory_Submit); - Assert.state(jsonMessage.success(), "分发 " + machineNodeModel.getName() + " 节点白名单失败" + jsonMessage.getMsg()); + Assert.state(jsonMessage.success(), "分发 " + machineNodeModel.getName() + " 节点授权失败" + jsonMessage.getMsg()); } return JsonMessage.success("保存成功"); } @@ -174,8 +188,8 @@ public class MachineNodeController extends BaseGroupNameController { @Feature(cls = ClassFeature.SYSTEM_CONFIG, method = MethodFeature.EDIT) @SystemPermission(superUser = true) public IJsonMessage saveNodeConfig(@ValidatorItem(msg = "请选择分发的机器") String ids, - String content, - String restart) { + String content, + String restart) { List idList = StrUtil.splitTrim(ids, StrUtil.COMMA); for (String s : idList) { MachineNodeModel machineNodeModel = machineNodeServer.getByKey(s); @@ -188,4 +202,69 @@ public class MachineNodeController extends BaseGroupNameController { } return JsonMessage.success("修改成功"); } + + /** + * 查询集群孤独的数据 + * + * @param id 集群ID + * @return json + */ + @GetMapping(value = "lonely-data", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage lonelyData(@ValidatorItem String id) { + MachineNodeModel machineNodeModel = machineNodeServer.getByKey(id); + Assert.notNull(machineNodeModel, "没有对应的机器"); + List models = projectInfoCacheService.lonelyDataArray(machineNodeModel); + List scriptCacheModels = nodeScriptServer.lonelyDataArray(machineNodeModel); + JSONObject jsonObject = new JSONObject(); + jsonObject.put("projects", models); + jsonObject.put("scripts", scriptCacheModels); + return JsonMessage.success("", jsonObject); + } + + @PostMapping(value = "correct-lonely-data", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage correctLonelyData(@ValidatorItem String id, + @ValidatorItem String type, + @ValidatorItem String dataId, + @ValidatorItem String toNodeId) { + MachineNodeModel machineNodeModel = machineNodeServer.getByKey(id); + Assert.notNull(machineNodeModel, "没有对应的机器"); + { + NodeModel nodeModel = nodeService.getByKey(toNodeId); + Assert.notNull(nodeModel, "没有对应的节点"); + Assert.hasText(nodeModel.getWorkspaceId(), "节点没有工作空间"); + Assert.state(StrUtil.equals(nodeModel.getMachineId(), machineNodeModel.getId()), "资产集群和节点不匹配"); + NodeUrl nodeUrl; + if (StrUtil.equalsIgnoreCase(type, "script")) { + nodeUrl = NodeUrl.Script_ChangeWorkspaceId; + } else if (StrUtil.equalsIgnoreCase(type, "project")) { + nodeUrl = NodeUrl.Manage_ChangeWorkspaceId; + } else { + throw new IllegalArgumentException("不支持的类型:" + type); + } + JSONObject jsonObject = new JSONObject(); + jsonObject.put("newWorkspaceId", nodeModel.getWorkspaceId()); + jsonObject.put("newNodeId", toNodeId); + jsonObject.put("id", dataId); + JsonMessage jsonMessage = NodeForward.request(machineNodeModel, nodeUrl, jsonObject); + if (!jsonMessage.success()) { + return new JsonMessage<>(406, "修正数据失败:" + jsonMessage.getMsg()); + } + } + // 重新同步节点数据 + { + NodeModel nodeModel = new NodeModel(); + nodeModel.setMachineId(id); + List modelList = nodeService.listByBean(nodeModel); + for (NodeModel model : modelList) { + if (StrUtil.equalsIgnoreCase(type, "script")) { + nodeScriptServer.syncExecuteNode(model); + } else if (StrUtil.equalsIgnoreCase(type, "project")) { + projectInfoCacheService.syncExecuteNode(model); + } + } + } + return JsonMessage.success("修正成功"); + } } diff --git a/modules/server/src/main/java/org/dromara/jpom/func/assets/server/MachineNodeServer.java b/modules/server/src/main/java/org/dromara/jpom/func/assets/server/MachineNodeServer.java index 31660b302..fb8ed2351 100644 --- a/modules/server/src/main/java/org/dromara/jpom/func/assets/server/MachineNodeServer.java +++ b/modules/server/src/main/java/org/dromara/jpom/func/assets/server/MachineNodeServer.java @@ -47,6 +47,8 @@ import org.dromara.jpom.common.forward.NodeForward; import org.dromara.jpom.common.forward.NodeUrl; import org.dromara.jpom.configuration.NodeConfig; import org.dromara.jpom.cron.CronUtils; +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.model.MachineNodeStatLogModel; import org.dromara.jpom.func.system.service.ClusterInfoService; @@ -54,8 +56,6 @@ import org.dromara.jpom.model.data.NodeModel; import org.dromara.jpom.model.user.UserModel; import org.dromara.jpom.service.h2db.BaseDbService; import org.dromara.jpom.service.node.NodeService; -import org.dromara.jpom.system.AgentException; -import org.dromara.jpom.system.AuthorizeException; import org.dromara.jpom.system.ServerConfig; import org.dromara.jpom.system.db.InitDb; import org.springframework.context.ApplicationContext; @@ -69,7 +69,6 @@ import java.util.Map; import java.util.Optional; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; -import java.util.function.Function; import java.util.stream.Collectors; /** @@ -252,7 +251,7 @@ public class MachineNodeServer extends BaseDbService implement } jsonObject.put("networkDelay", networkTime); this.saveStatInfo(machineNodeModel, jsonObject); - } catch (AuthorizeException agentException) { + } catch (AgentAuthorizeException agentException) { this.updateStatus(machineNodeModel, 2, agentException.getMessage()); } catch (AgentException e) { this.updateStatus(machineNodeModel, 0, e.getMessage()); @@ -283,9 +282,9 @@ public class MachineNodeServer extends BaseDbService implement networkDelay = networkDelay - systemSleep; machineNodeModel.setNetworkDelay(networkDelay); // jpom 相关信息 - Optional.ofNullable(data.getJSONObject("jpomInfo")).ifPresent(jsonObject -> { - JSONObject jpomManifest = jsonObject.getJSONObject("jpomManifest"); - Optional.ofNullable(jpomManifest) + JSONObject jpomInfo = data.getJSONObject("jpomInfo"); + Optional.ofNullable(jpomInfo).ifPresent(jsonObject -> { + Optional.ofNullable(jsonObject.getJSONObject("jpomManifest")) .ifPresent(jsonObject1 -> { machineNodeModel.setJpomVersion(jsonObject1.getString("version")); machineNodeModel.setJpomBuildTime(jsonObject1.getString("timeStamp")); @@ -342,6 +341,26 @@ public class MachineNodeServer extends BaseDbService implement }); this.updateById(machineNodeModel); machineNodeStatLogServer.insert(machineNodeStatLogModel); + // + Optional.ofNullable(jpomInfo).ifPresent(jsonObject -> { + JSONObject workspaceStat = jsonObject.getJSONObject("workspaceStat"); + if (workspaceStat == null) { + return; + } + for (Map.Entry entry : workspaceStat.entrySet()) { + String key = entry.getKey(); + JSONObject value = (JSONObject) entry.getValue(); + int projectCount = value.getIntValue("projectCount", 0); + int scriptCount = value.getIntValue("scriptCount", 0); + Entity entity = Entity.create(); + entity.set("jpomProjectCount", projectCount); + entity.set("jpomScriptCount", scriptCount); + Entity where = Entity.create(); + where.set("machineId", machineNodeModel.getId()); + where.set("workspaceId", key); + nodeService.update(entity, where); + } + }); } /** diff --git a/modules/server/src/main/java/org/dromara/jpom/func/cert/controller/CertificateInfoController.java b/modules/server/src/main/java/org/dromara/jpom/func/cert/controller/CertificateInfoController.java index 6e03b0bb8..1f95131a4 100644 --- a/modules/server/src/main/java/org/dromara/jpom/func/cert/controller/CertificateInfoController.java +++ b/modules/server/src/main/java/org/dromara/jpom/func/cert/controller/CertificateInfoController.java @@ -305,8 +305,8 @@ public class CertificateInfoController extends BaseServerController { @PostMapping(value = "edit", produces = MediaType.APPLICATION_JSON_VALUE) @Feature(method = MethodFeature.EDIT) public IJsonMessage edit(@ValidatorItem String id, - String description, - HttpServletRequest request) throws IOException { + String description, + HttpServletRequest request) throws IOException { // 验证权限 certificateInfoService.getByKeyAndGlobal(id, request); @@ -345,18 +345,18 @@ public class CertificateInfoController extends BaseServerController { @PostMapping(value = "deploy", produces = MediaType.APPLICATION_JSON_VALUE) @Feature(method = MethodFeature.EDIT) public IJsonMessage addTask(@ValidatorItem String id, - @ValidatorItem String name, - @ValidatorItem(value = ValidatorRule.NUMBERS) int taskType, - @ValidatorItem String taskDataIds, - @ValidatorItem String releasePathParent, - @ValidatorItem String releasePathSecondary, - String beforeScript, - String afterScript, - HttpServletRequest request) { + @ValidatorItem String name, + @ValidatorItem(value = ValidatorRule.NUMBERS) int taskType, + @ValidatorItem String taskDataIds, + @ValidatorItem String releasePathParent, + @ValidatorItem String releasePathSecondary, + String beforeScript, + String afterScript, + HttpServletRequest request) { // 判断参数 ServerWhitelist configDeNewInstance = outGivingWhitelistService.getServerWhitelistData(request); - List whitelistServerOutGiving = configDeNewInstance.outGiving(); - Assert.state(AgentWhitelist.checkPath(whitelistServerOutGiving, releasePathParent), "请选择正确的项目路径,或者还没有配置白名单"); + List whitelistServerOutGiving = configDeNewInstance.getOutGiving(); + Assert.state(AgentWhitelist.checkPath(whitelistServerOutGiving, releasePathParent), "请选择正确的项目路径,或者还没有配置授权"); Assert.hasText(releasePathSecondary, "请填写发布文件的二级目录"); // 判断证书是否存在 CertificateInfoModel model = certificateInfoService.getByKeyAndGlobal(id, request); @@ -376,7 +376,7 @@ public class CertificateInfoController extends BaseServerController { // 创建发布任务 Map env = new HashMap<>(); env.put("CERT_SERIAL_NUMBER_STR", model.getSerialNumberStr()); - return fileReleaseTaskService.addTask(fileId, name, taskType, taskDataIds, releasePath, beforeScript, afterScript, env, request); + return fileReleaseTaskService.addTask(fileId, 1, name, taskType, taskDataIds, releasePath, beforeScript, afterScript, env, request); } finally { FileUtil.del(tempSave); } diff --git a/modules/server/src/main/java/org/dromara/jpom/func/cert/service/CertificateInfoService.java b/modules/server/src/main/java/org/dromara/jpom/func/cert/service/CertificateInfoService.java index 4103160df..b2500f76c 100644 --- a/modules/server/src/main/java/org/dromara/jpom/func/cert/service/CertificateInfoService.java +++ b/modules/server/src/main/java/org/dromara/jpom/func/cert/service/CertificateInfoService.java @@ -29,6 +29,7 @@ import cn.hutool.crypto.GlobalBouncyCastleProvider; import cn.hutool.crypto.KeyUtil; import cn.hutool.crypto.PemUtil; import cn.hutool.crypto.SecureUtil; +import cn.hutool.crypto.asymmetric.ECIES; import cn.hutool.crypto.asymmetric.KeyType; import cn.hutool.crypto.asymmetric.RSA; import cn.hutool.db.Entity; @@ -256,12 +257,22 @@ public class CertificateInfoService extends BaseGlobalOrWorkspaceService addTask(@ValidatorItem String fileId, - @ValidatorItem String name, - @ValidatorItem(value = ValidatorRule.NUMBERS) int taskType, - @ValidatorItem String taskDataIds, - @ValidatorItem String releasePathParent, - @ValidatorItem String releasePathSecondary, - String beforeScript, - String afterScript, - HttpServletRequest request) { + @ValidatorItem(value = ValidatorRule.NUMBERS) Integer fileType, + @ValidatorItem String name, + @ValidatorItem(value = ValidatorRule.NUMBERS) int taskType, + @ValidatorItem String taskDataIds, + @ValidatorItem String releasePathParent, + @ValidatorItem String releasePathSecondary, + String beforeScript, + String afterScript, + HttpServletRequest request) { // 判断参数 ServerWhitelist configDeNewInstance = outGivingWhitelistService.getServerWhitelistData(request); - List whitelistServerOutGiving = configDeNewInstance.outGiving(); - Assert.state(AgentWhitelist.checkPath(whitelistServerOutGiving, releasePathParent), "请选择正确的项目路径,或者还没有配置白名单"); + List whitelistServerOutGiving = configDeNewInstance.getOutGiving(); + Assert.state(AgentWhitelist.checkPath(whitelistServerOutGiving, releasePathParent), "请选择正确的项目路径,或者还没有配置授权"); Assert.hasText(releasePathSecondary, "请填写发布文件的二级目录"); String releasePath = FileUtil.normalize(releasePathParent + StrUtil.SLASH + releasePathSecondary); - return fileReleaseTaskService.addTask(fileId, name, taskType, taskDataIds, releasePath, beforeScript, afterScript, null, request); + return fileReleaseTaskService.addTask(fileId, fileType, name, taskType, taskDataIds, releasePath, beforeScript, afterScript, null, request); } + /** + * 重建-重新发布 + * + * @param fileId 文件id + * @param name 任务名 + * @param taskType 任务类型 + * @param taskDataIds 任务关联数据id + * @param parentTaskId 父级任务id + * @param beforeScript 发布之前的脚步 + * @param afterScript 发布之后的脚步 + * @param request 请求 + * @return json + */ @PostMapping(value = "re-task", produces = MediaType.APPLICATION_JSON_VALUE) @Feature(method = MethodFeature.EDIT) public IJsonMessage reTask(@ValidatorItem String fileId, @@ -109,7 +124,9 @@ public class FileReleaseTaskController extends BaseServerController { HttpServletRequest request) { FileReleaseTaskLogModel parentTask = fileReleaseTaskService.getByKey(parentTaskId, request); Assert.notNull(parentTask, "父任务不存在"); - return fileReleaseTaskService.addTask(fileId, name, taskType, taskDataIds, parentTask.getReleasePath(), beforeScript, afterScript, null, request); + Integer fileType = parentTask.getFileType(); + fileType = ObjectUtil.defaultIfNull(fileType, 1); + return fileReleaseTaskService.addTask(fileId, fileType, name, taskType, taskDataIds, parentTask.getReleasePath(), beforeScript, afterScript, null, request); } /** @@ -202,8 +219,8 @@ public class FileReleaseTaskController extends BaseServerController { @GetMapping(value = "log-list", produces = MediaType.APPLICATION_JSON_VALUE) @Feature(method = MethodFeature.LIST) public IJsonMessage log(@ValidatorItem(value = ValidatorRule.NOT_BLANK, msg = "没有数据") String id, - @ValidatorItem(value = ValidatorRule.POSITIVE_INTEGER, msg = "line") int line, - HttpServletRequest request) { + @ValidatorItem(value = ValidatorRule.POSITIVE_INTEGER, msg = "line") int line, + HttpServletRequest request) { FileReleaseTaskLogModel item = fileReleaseTaskService.getByKey(id, request); Assert.notNull(item, "没有对应数据"); File file = fileReleaseTaskService.logFile(item); diff --git a/modules/server/src/main/java/org/dromara/jpom/func/files/controller/StaticFileStorageController.java b/modules/server/src/main/java/org/dromara/jpom/func/files/controller/StaticFileStorageController.java new file mode 100644 index 000000000..99651771f --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/func/files/controller/StaticFileStorageController.java @@ -0,0 +1,222 @@ +/* + * 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.func.files.controller; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.StrUtil; +import cn.keepbx.jpom.IJsonMessage; +import cn.keepbx.jpom.model.JsonMessage; +import org.dromara.jpom.common.BaseServerController; +import org.dromara.jpom.common.ServerConst; +import org.dromara.jpom.common.ServerOpenApi; +import org.dromara.jpom.common.UrlRedirectUtil; +import org.dromara.jpom.common.validator.ValidatorItem; +import org.dromara.jpom.func.files.model.StaticFileStorageModel; +import org.dromara.jpom.func.files.service.FileStorageService; +import org.dromara.jpom.func.files.service.StaticFileStorageService; +import org.dromara.jpom.model.PageResultDto; +import org.dromara.jpom.model.user.UserModel; +import org.dromara.jpom.permission.ClassFeature; +import org.dromara.jpom.permission.Feature; +import org.dromara.jpom.permission.MethodFeature; +import org.dromara.jpom.service.user.TriggerTokenLogServer; +import org.springframework.http.MediaType; +import org.springframework.util.Assert; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; +import java.io.IOException; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * @author bwcx_jzy + * @since 23/12/28 028 + */ +@RestController +@RequestMapping(value = "/file-storage/static") +@Feature(cls = ClassFeature.STATIC_FILE_STORAGE) +public class StaticFileStorageController extends BaseServerController { + + private final StaticFileStorageService staticFileStorageService; + private final FileStorageService fileStorageService; + private final TriggerTokenLogServer triggerTokenLogServer; + + public StaticFileStorageController(StaticFileStorageService staticFileStorageService, + FileStorageService fileStorageService, + TriggerTokenLogServer triggerTokenLogServer) { + this.staticFileStorageService = staticFileStorageService; + this.fileStorageService = fileStorageService; + this.triggerTokenLogServer = triggerTokenLogServer; + } + + /** + * 分页列表 + * + * @return json + */ + @PostMapping(value = "list", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage> list(HttpServletRequest request) { + // + String workspace = fileStorageService.getCheckUserWorkspace(request); + PageResultDto listPage = staticFileStorageService.listPage(request, workspace); + return JsonMessage.success("", listPage); + } + + @GetMapping(value = "del", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.DEL) + public IJsonMessage del(String id, String ids, @ValidatorItem Boolean thorough, HttpServletRequest request) throws IOException { + this.delItem(id, thorough, request); + List list = StrUtil.splitTrim(ids, StrUtil.COMMA); + for (String s : list) { + this.delItem(s, thorough, request); + } + return JsonMessage.success("删除成功"); + } + + /** + * 删除数据 + * + * @param id id + * @param thorough 是否彻底删除 + * @param request 请求 + */ + private void delItem(String id, Boolean thorough, HttpServletRequest request) { + if (StrUtil.isEmpty(id)) { + return; + } + StaticFileStorageModel storageModel = staticFileStorageService.getByKey(id); + if (storageModel == null) { + return; + } + this.checkStaticDir(storageModel, request); + // + if (thorough != null && thorough) { + FileUtil.del(storageModel.getAbsolutePath()); + } + // + staticFileStorageService.delByKey(id); + } + + /** + * 判断是否有权限操作 + * + * @param storageModel 静态文件 + */ + private void checkStaticDir(StaticFileStorageModel storageModel, HttpServletRequest request) { + String workspace = fileStorageService.getCheckUserWorkspace(request); + staticFileStorageService.checkStaticDir(storageModel, workspace); + } + + /** + * get a trigger url + * + * @param id id + * @return json + */ + @GetMapping(value = "trigger-url", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EDIT) + public IJsonMessage> getTriggerUrl(@ValidatorItem String id, String rest, HttpServletRequest request) { + UserModel user = getUser(); + // 查询当前工作空间 + StaticFileStorageModel item = staticFileStorageService.getByKey(id); + + this.checkStaticDir(item, request); + // + StaticFileStorageModel updateInfo; + if (StrUtil.isEmpty(item.getTriggerToken()) || StrUtil.isNotEmpty(rest)) { + updateInfo = new StaticFileStorageModel(); + updateInfo.setId(id); + updateInfo.setTriggerToken(triggerTokenLogServer.restToken(item.getTriggerToken(), staticFileStorageService.typeName(), + item.getId(), user.getId())); + staticFileStorageService.updateById(updateInfo); + } else { + updateInfo = item; + } + Map map = this.getBuildToken(updateInfo, request); + return JsonMessage.success(StrUtil.isEmpty(rest) ? "ok" : "重置成功", map); + } + + private Map getBuildToken(StaticFileStorageModel item, HttpServletRequest request) { + String contextPath = UrlRedirectUtil.getHeaderProxyPath(request, ServerConst.PROXY_PATH); + Map map = new HashMap<>(10); + { + String url = ServerOpenApi.STATIC_FILE_STORAGE_DOWNLOAD. + replace("{id}", item.getId()). + replace("{token}", item.getTriggerToken()); + String triggerBuildUrl = String.format("/%s/%s", contextPath, url); + map.put("triggerDownloadUrl", FileUtil.normalize(triggerBuildUrl)); + } + map.put("id", item.getId()); + map.put("token", item.getTriggerToken()); + return map; + } + + @PostMapping(value = "edit", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.EDIT) + public IJsonMessage edit(@ValidatorItem String id, + String description, + HttpServletRequest request) throws IOException { + StaticFileStorageModel storageModel = staticFileStorageService.getByKey(id); + this.checkStaticDir(storageModel, request); + StaticFileStorageModel fileStorageModel = new StaticFileStorageModel(); + fileStorageModel.setId(id); + fileStorageModel.setDescription(description); + staticFileStorageService.updateById(fileStorageModel); + return JsonMessage.success("修改成功"); + } + + /** + * 判断是否存在文件 + * + * @param fileId 文件id + * @return json + */ + @GetMapping(value = "has-file", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage hasFile(@ValidatorItem String fileId, HttpServletRequest request) { + StaticFileStorageModel storageModel = staticFileStorageService.getByKey(fileId); + this.checkStaticDir(storageModel, request); + return JsonMessage.success("", storageModel); + } + + /** + * 重新扫描 + * + * @return json + */ + @GetMapping(value = "scanner", produces = MediaType.APPLICATION_JSON_VALUE) + @Feature(method = MethodFeature.LIST) + public IJsonMessage scanner(HttpServletRequest request) { + boolean scanning = staticFileStorageService.isScanning(); + Assert.state(!scanning, "当前正在扫描中"); + String workspace = fileStorageService.getCheckUserWorkspace(request); + staticFileStorageService.scanByWorkspace(workspace); + return JsonMessage.success("扫描成功"); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/func/files/model/FileReleaseTaskLogModel.java b/modules/server/src/main/java/org/dromara/jpom/func/files/model/FileReleaseTaskLogModel.java index 6461ca55c..63dead198 100644 --- a/modules/server/src/main/java/org/dromara/jpom/func/files/model/FileReleaseTaskLogModel.java +++ b/modules/server/src/main/java/org/dromara/jpom/func/files/model/FileReleaseTaskLogModel.java @@ -58,6 +58,12 @@ public class FileReleaseTaskLogModel extends BaseWorkspaceModel { * @see FileStorageModel#getId() */ private String fileId; + /** + * 文件来源类型 + * 1 文件中心 + * 2 静态文件 + */ + private Integer fileType; /** * 任务类型 0 ssh 1 节点 */ diff --git a/modules/server/src/main/java/org/dromara/jpom/func/files/model/FileStorageModel.java b/modules/server/src/main/java/org/dromara/jpom/func/files/model/FileStorageModel.java index 02bd36fe0..5e973d75d 100644 --- a/modules/server/src/main/java/org/dromara/jpom/func/files/model/FileStorageModel.java +++ b/modules/server/src/main/java/org/dromara/jpom/func/files/model/FileStorageModel.java @@ -43,7 +43,7 @@ import org.dromara.jpom.model.BaseWorkspaceModel; @TableName(value = "FILE_STORAGE", name = "文件管理中心") @Data @NoArgsConstructor -public class FileStorageModel extends BaseWorkspaceModel { +public class FileStorageModel extends BaseWorkspaceModel implements IFileStorage { @Override public void setId(String id) { diff --git a/modules/server/src/main/java/org/dromara/jpom/func/files/model/IFileStorage.java b/modules/server/src/main/java/org/dromara/jpom/func/files/model/IFileStorage.java new file mode 100644 index 000000000..d1629ec50 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/func/files/model/IFileStorage.java @@ -0,0 +1,34 @@ +/* + * 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.func.files.model; + +/** + * @author bwcx_jzy + * @since 23/12/28 028 + */ +public interface IFileStorage { + + String getName(); + + String getExtName(); +} diff --git a/modules/server/src/main/java/org/dromara/jpom/func/files/model/StaticFileStorageModel.java b/modules/server/src/main/java/org/dromara/jpom/func/files/model/StaticFileStorageModel.java new file mode 100644 index 000000000..51fc7d21d --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/func/files/model/StaticFileStorageModel.java @@ -0,0 +1,113 @@ +/* + * 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.func.files.model; + +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.StrUtil; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import org.dromara.jpom.db.TableName; +import org.dromara.jpom.model.BaseUserModifyDbModel; + +/** + * @author bwcx_jzy + * @since 23/12/28 028 + */ +@EqualsAndHashCode(callSuper = true) +@TableName(value = "STATIC_FILE_STORAGE", name = "静态文件管理") +@Data +@NoArgsConstructor +public class StaticFileStorageModel extends BaseUserModifyDbModel implements IFileStorage { + + /** + * 文件名 + */ + private String name; + + /** + * 只保留 240 字符 + * + * @param name 名称 + */ + public void setName(String name) { + this.name = StrUtil.maxLength(name, 240); + } + + /** + * 文件大小 + */ + private Long size; + /** + * 文件路径 + */ + private String absolutePath; + private String parentAbsolutePath; + /** + * 要组索引不字段不能太长 + * [42000][1071] Specified key was too long; max key length is 3072 bytes + */ + private String staticDir; + private Integer level; + /** + * 文件修改时间 + */ + private Long lastModified; + /** + * 文件扩展名 + */ + private String extName; + /** + * 文件状态 + * 0 不存在 + * 1 存在 + */ + private Integer status; + /** + * 文件类型 + * 0 文件夹 + * 1 文件 + */ + private Integer type; + /** + * 扫描任务id + */ + private Long scanTaskId; + /** + * 描述 + */ + private String description; + /** + * 触发器 token + */ + private String triggerToken; + + @Override + protected boolean hasCreateUser() { + return false; + } + + public int type() { + return ObjectUtil.defaultIfNull(this.getType(), 0); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/func/files/service/FileReleaseTaskService.java b/modules/server/src/main/java/org/dromara/jpom/func/files/service/FileReleaseTaskService.java index 5f1e014ff..046391cc5 100644 --- a/modules/server/src/main/java/org/dromara/jpom/func/files/service/FileReleaseTaskService.java +++ b/modules/server/src/main/java/org/dromara/jpom/func/files/service/FileReleaseTaskService.java @@ -25,7 +25,9 @@ package org.dromara.jpom.func.files.service; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.date.SystemClock; import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IoUtil; import cn.hutool.core.lang.Opt; +import cn.hutool.core.lang.Tuple; import cn.hutool.core.map.SafeConcurrentHashMap; import cn.hutool.core.stream.CollectorUtil; import cn.hutool.core.thread.ThreadUtil; @@ -45,6 +47,8 @@ import org.dromara.jpom.common.forward.NodeUrl; import org.dromara.jpom.func.assets.model.MachineSshModel; import org.dromara.jpom.func.files.model.FileReleaseTaskLogModel; import org.dromara.jpom.func.files.model.FileStorageModel; +import org.dromara.jpom.func.files.model.IFileStorage; +import org.dromara.jpom.func.files.model.StaticFileStorageModel; import org.dromara.jpom.model.EnvironmentMapBuilder; import org.dromara.jpom.model.PageResultDto; import org.dromara.jpom.model.data.NodeModel; @@ -89,6 +93,7 @@ public class FileReleaseTaskService extends BaseWorkspaceService cancelTag = new SafeConcurrentHashMap<>(); @@ -98,7 +103,8 @@ public class FileReleaseTaskService extends BaseWorkspaceService addTask(String fileId, + Integer fileType, String name, int taskType, String taskDataIds, @@ -146,11 +188,19 @@ public class FileReleaseTaskService extends BaseWorkspaceService env, HttpServletRequest request) { - FileStorageModel storageModel = fileStorageService.getByKey(fileId, request); - Assert.notNull(storageModel, "不存在对应的文件"); - File storageSavePath = serverConfig.fileStorageSavePath(); - File file = FileUtil.file(storageSavePath, storageModel.getPath()); - Assert.state(FileUtil.isFile(file), "当前文件丢失不能执行发布任务"); + Tuple tuple; + switch (fileType) { + case 1: + tuple = this.getFileStorage(fileId, request); + break; + case 2: + tuple = this.getStaticFileStorage(fileId, request); + break; + default: + throw new IllegalArgumentException("不支持的类型:" + fileType); + } + File file = tuple.get(0); + IFileStorage storageModel = tuple.get(1); // List list; if (taskType == 0) { @@ -171,6 +221,7 @@ public class FileReleaseTaskService extends BaseWorkspaceService env, FileStorageModel storageModel) { + private void startTask(String taskId, File storageSaveFile, Map env, IFileStorage storageModel) { FileReleaseTaskLogModel taskRoot = this.getByKey(taskId); Assert.notNull(taskRoot, "没有找到父级任务"); // @@ -283,17 +335,18 @@ public class FileReleaseTaskService extends BaseWorkspaceService { String modelId = model.getId(); + LogRecorder logRecorder = null; try { this.updateStatus(taskId, modelId, 1, "开始发布文件"); File logFile = logFile(model); - LogRecorder logRecorder = LogRecorder.builder().file(logFile).charset(CharsetUtil.CHARSET_UTF_8).build(); + logRecorder = LogRecorder.builder().file(logFile).charset(CharsetUtil.CHARSET_UTF_8).build(); NodeModel item = nodeService.getByKey(model.getTaskDataId()); if (item == null) { logRecorder.systemError("没有找到对应的节点项:{}", model.getTaskDataId()); @@ -313,6 +366,7 @@ public class FileReleaseTaskService extends BaseWorkspaceService progressRangeList = ConcurrentHashMap.newKeySet((int) Math.floor((float) 100 / buildExtConfig.getLogReduceProgressRatio())); String name = storageModel.getName(); name = StrUtil.wrapIfMissing(name, StrUtil.EMPTY, StrUtil.DOT + storageModel.getExtName()); + LogRecorder finalLogRecorder = logRecorder; JsonMessage jsonMessage = NodeForward.requestSharding(item, NodeUrl.Manage_File_Upload_Sharding2, data, storageSaveFile, name, sliceData -> { sliceData.putAll(data); @@ -323,7 +377,7 @@ public class FileReleaseTaskService extends BaseWorkspaceService implements IAsyncLoad, Task, Watcher, DisposableBean, ITriggerToken { + + private final FileStorageConfig fileStorageConfig; + private final WorkspaceService workspaceService; + private final OutGivingWhitelistService outGivingWhitelistService; + private Map watchMonitor; + /** + * 扫描任务进行中 + */ + private volatile boolean scanning = false; + + public StaticFileStorageService(ServerConfig serverConfig, + WorkspaceService workspaceService, + OutGivingWhitelistService outGivingWhitelistService) { + this.fileStorageConfig = serverConfig.getFileStorage(); + this.workspaceService = workspaceService; + this.outGivingWhitelistService = outGivingWhitelistService; + } + + /** + * 是否在扫描中 + * + * @return true 扫描中 + */ + public boolean isScanning() { + return scanning; + } + + /** + * 分页查询 + * + * @param request 请求对象 + * @param workspaceId 工作空间id + * @return page + */ + public PageResultDto listPage(HttpServletRequest request, String workspaceId) { + Map paramMap = ServletUtil.getParamMap(request); + ServerWhitelist whitelistData = outGivingWhitelistService.getServerWhitelistData(workspaceId); + List staticDir = whitelistData.staticDir(); + if (CollUtil.isEmpty(staticDir)) { + return new PageResultDto<>(1, 1, 0); + } + paramMap.remove("staticDir"); + paramMap.put("staticDir:in", CollUtil.join(staticDir, ",")); + return super.listPage(paramMap, true); + } + + @Override + public void startLoad() { + String scanStaticDirCron = fileStorageConfig.getScanStaticDirCron(); + if (StrUtil.isEmpty(scanStaticDirCron)) { + log.debug("未开启静态文件扫描"); + this.removeTask(); + return; + } + List list = this.staticDir(); + if (CollUtil.isEmpty(list)) { + log.debug("未配置静态目录"); + this.removeTask(); + return; + } + // 先关闭已经存在的监听器 + this.closeWatchMonitor(); + CronUtils.upsert("scan-static-dir", scanStaticDirCron, this); + Boolean monitorStaticDir = fileStorageConfig.getWatchMonitorStaticDir(); + int watchMonitorMaxDepth = ObjectUtil.defaultIfNull(fileStorageConfig.getWatchMonitorMaxDepth(), 0); + if (monitorStaticDir != null && monitorStaticDir) { + // 开启任务 + watchMonitor = new HashMap<>(list.size()); + for (String s : list) { + File file = FileUtil.file(s); + if (!FileUtil.exist(file)) { + log.warn("被监控的目录不存在忽略创建监听器:{}", s); + // 自动删除已经存在的任务 + this.delete(file); + continue; + } + DelayWatcher delayWatcher = new DelayWatcher(StaticFileStorageService.this, 1000); + WatchMonitor monitor = WatchUtil.createAll(s, watchMonitorMaxDepth, delayWatcher); + watchMonitor.put(s, monitor); + // 开启任务 + ThreadUtil.execute(monitor); + } + } + } + + /** + * 关闭监听 + */ + private void closeWatchMonitor() { + if (watchMonitor == null) { + return; + } + for (WatchMonitor value : watchMonitor.values()) { + IoUtil.close(value); + } + watchMonitor = null; + } + + /** + * 关闭任务 + */ + private void removeTask() { + CronUtils.remove("scan-static-dir"); + this.closeWatchMonitor(); + } + + /** + * 扫描指定工作空间 + * + * @param workspaceId 工作空间id + */ + public void scanByWorkspace(String workspaceId) { + try { + this.scanning = true; + ServerWhitelist serverWhitelistData = outGivingWhitelistService.getServerWhitelistData(workspaceId); + List stringList = serverWhitelistData.staticDir(); + Assert.notEmpty(stringList, "还未配置静态目录"); + this.scanList(stringList); + } finally { + this.scanning = false; + } + } + + /** + * 扫描指定目录 + * + * @param list 目录 + */ + private void scanList(List list) { + Snowflake snowflake = IdUtil.getSnowflake(); + long taskId = snowflake.nextId(); + for (String item : list) { + // 开始扫描目录 + this.scanItem(item, item, 0, taskId); + // 更新文件状态 + String sql = StrUtil.format("update {} set status=0 where staticDir=? and scanTaskId <> ?", this.getTableName()); + this.execute(sql, item, taskId); + } + } + + @Override + public void execute() { + try { + this.scanning = true; + List list = this.staticDir(); + if (CollUtil.isEmpty(list)) { + log.warn("当前没有配置静态目录,自动取消定时任务"); + this.removeTask(); + return; + } + this.scanList(list); + } finally { + this.scanning = false; + } + } + + /** + * 扫描目录 + * + * @param staticDir 静态目录 + * @param item 开始目录 + * @param level 目前层级 + * @param taskId 任务id + */ + private void scanItem(String staticDir, String item, int level, Long taskId) { + File file = FileUtil.file(item); + if (!FileUtil.exist(file)) { + // 目录不存在了,自动删除 + this.delete(file); + return; + } + if (FileUtil.isFile(file)) { + // 文件夹 + this.doFile(file, staticDir, level, taskId, 1); + } else if (FileUtil.isDirectory(file)) { + // 处理自身 + this.doFile(file, staticDir, level, taskId, 0); + File[] files = file.listFiles(); + if (files == null) { + return; + } + for (File subFile : files) { + this.scanItem(staticDir, subFile.getAbsolutePath(), level + 1, taskId); + } + } else { + log.warn("不支持的文件类型:{}", file.getAbsolutePath()); + } + } + + /** + * 处理文件缓存对象 + * + * @param file 文件 + * @param staticDir 静态目录 + * @param level 层级 + * @param taskId 任务id + * @param fileType 文件类型 + */ + private void doFile(File file, String staticDir, int level, Long taskId, int fileType) { + String name = file.getName(); + String absolutePath = this.absNormalize(file); + long length = file.length(); + long lastModified = file.lastModified(); + String parentAbsolutePath = this.absNormalize(file.getParentFile()); + if (StrUtil.length(absolutePath) > 500) { + log.warn("文件目录长度超过 500 ,自动忽略此类文件:{}", absolutePath); + return; + } + StaticFileStorageModel storageModel = new StaticFileStorageModel(); + storageModel.setId(SecureUtil.md5(absolutePath)); + storageModel.setName(name); + storageModel.setAbsolutePath(absolutePath); + storageModel.setParentAbsolutePath(parentAbsolutePath); + storageModel.setLevel(level); + storageModel.setStaticDir(staticDir); + storageModel.setStatus(1); + storageModel.setType(fileType); + storageModel.setScanTaskId(taskId); + storageModel.setExtName(FileUtil.extName(file)); + storageModel.setLastModified(lastModified); + storageModel.setSize(length); + this.upsert(storageModel); + } + + private String absNormalize(File file) { + String absolutePath = file.getAbsolutePath(); + return FileUtil.normalize(absolutePath); + } + + /** + * 处理文件缓存对象 + * + * @param path 文件 + * @param staticDirPath 静态目录 + */ + private void doFile(Path path, Path staticDirPath) { + try { + File file = path.toFile(); + String absolutePath = this.absNormalize(file); + File parentFile = file.getParentFile(); + String parentAbsolutePath = this.absNormalize(parentFile); + if (StrUtil.length(absolutePath) > 500) { + log.warn("文件目录长度超过 500 ,自动忽略此类文件:{}", absolutePath); + return; + } + // 计算层级 + File staticDir = staticDirPath.toFile(); + String staticStr = this.absNormalize(staticDir); + String subPath = FileUtil.subPath(staticStr, absolutePath); + int level = CollUtil.size(StrUtil.splitTrim(subPath, StrUtil.SLASH)); + StaticFileStorageModel storageModel = new StaticFileStorageModel(); + storageModel.setId(SecureUtil.md5(absolutePath)); + storageModel.setName(file.getName()); + storageModel.setAbsolutePath(absolutePath); + storageModel.setParentAbsolutePath(parentAbsolutePath); + storageModel.setLevel(level + 1); + storageModel.setStaticDir(staticStr); + storageModel.setStatus(1); + if (FileUtil.isFile(file)) { + // 文件夹 + storageModel.setType(1); + } else if (FileUtil.isDirectory(file)) { + storageModel.setType(0); + } else { + log.warn("不支持的文件类型:{}", absolutePath); + return; + } + storageModel.setExtName(FileUtil.extName(file)); + storageModel.setLastModified(file.lastModified()); + storageModel.setSize(file.length()); + this.upsert(storageModel); + // 判断类型 + if (storageModel.getType() == 0) { + // 文件夹类型 + // 需要扫描父级,可能避免删除无法正常监听到并且变更 + Snowflake snowflake = IdUtil.getSnowflake(); + long taskId = snowflake.nextId(); + this.scanItem(storageModel.getStaticDir(), absolutePath, level, taskId); + // 删除未被更新的数据 + StaticFileStorageModel where = new StaticFileStorageModel(); + where.setParentAbsolutePath(absolutePath); + List list = this.listByBean(where); + if (CollUtil.isNotEmpty(list)) { + List collect = list.stream() + .filter(staticFileStorageModel -> !ObjectUtil.equals(staticFileStorageModel.getScanTaskId(), taskId)) + .filter(staticFileStorageModel -> { + File file1 = FileUtil.file(staticFileStorageModel.getAbsolutePath()); + if (staticFileStorageModel.type() == 1) { + return !FileUtil.exist(file1); + } + // 删除子目录 + this.delete(staticFileStorageModel.getId(), staticFileStorageModel.getAbsolutePath()); + return !FileUtil.exist(file1); + }) + .map(BaseIdModel::getId) + .collect(Collectors.toList()); + this.update(Entity.create().set("status", 0), Entity.create().set("id", collect)); + } + } + } catch (Exception e) { + log.error("处理文件事件异常", e); + } + } + + /** + * 查询当前授权的静态目录 + * + * @return list + */ + private List staticDir() { + List list = workspaceService.list(); + if (list == null) { + return null; + } + return list.stream() + .map(workspaceModel -> outGivingWhitelistService.getServerWhitelistData(workspaceModel.getId())) + .map(ServerWhitelist::staticDir) + .filter(Objects::nonNull) + .flatMap(Collection::stream) + .distinct() + .collect(Collectors.toList()); + } + + /** + * 解下监听的 key + * + * @param path 路径 + * @return key + */ + private WatchKey getWatchKey(Path path) { + File file = path.toFile(); + String staticPath = this.absNormalize(file); + WatchMonitor watchMonitor1 = MapUtil.get(watchMonitor, staticPath, WatchMonitor.class); + Assert.notNull(watchMonitor1, "监听任务丢失"); + Map keyPathMap = (Map) ReflectUtil.getFieldValue(watchMonitor1, "watchKeyPathMap"); + Assert.notNull(keyPathMap, "没有找到监听的key"); + for (Map.Entry entry : keyPathMap.entrySet()) { + if (entry.getValue().equals(path)) { + return entry.getKey(); + } + } + throw new IllegalArgumentException("没有找到监听 key"); + } + + /** + * 解析事件全路径 + * + * @param currentPath 监听路径 + * @param context 上下文 + * @return path + */ + private Path fullPath(Path currentPath, Object context) { + WatchKey watchKey = getWatchKey(currentPath); + Path watchablePath = (Path) watchKey.watchable(); + return watchablePath.resolve((Path) context); + } + + @Override + public void onCreate(WatchEvent event, Path currentPath) { + log.debug("文件修改事件:{} {}", currentPath, event); + this.onModify2(event, currentPath); + } + + @Override + public void onModify(WatchEvent event, Path currentPath) { + log.debug("文件修改事件:{} {}", currentPath, event); + this.onModify2(event, currentPath); + } + + private void onModify2(WatchEvent event, Path currentPath) { + Object context = event.context(); + if (context instanceof Path) { + Path path = this.fullPath(currentPath, context); + log.debug("文件全路径:{}", path); + this.doFile(path, currentPath); + } else { + log.warn("不支持的事件类型:{}", context.getClass().getName()); + } + } + + @Override + public void onDelete(WatchEvent event, Path currentPath) { + log.debug("文件删除事件:{} {}", currentPath, event); + Object context = event.context(); + if (context instanceof Path) { + Path path = this.fullPath(currentPath, context); + File file = path.toFile(); + try { + // 处理文件删除事件 + this.delete(file); + } catch (Exception e) { + log.error("处理文件删除异常", e); + } + + } else { + log.warn("不支持的事件类型:{}", context.getClass().getName()); + } + } + + /** + * 删除文件 + * + * @param file 文件 + */ + private void delete(File file) { + String absolutePath = this.absNormalize(file); + String md5 = SecureUtil.md5(absolutePath); + this.delete(md5, absolutePath); + } + + /** + * 删除指定文件 + * + * @param md5 文件id + * @param absolutePath 文件路径 + */ + private void delete(String md5, String absolutePath) { + StaticFileStorageModel storageModel = new StaticFileStorageModel(); + storageModel.setId(md5); + storageModel.setStatus(0); + int update = this.updateById(storageModel); + log.debug("文件删除事件:{}", absolutePath); + if (update > 0) { + StaticFileStorageModel model = this.getByKey(md5); + if (model != null && model.type() == 0) { + // 文件夹 + StaticFileStorageModel where = new StaticFileStorageModel(); + where.setParentAbsolutePath(absolutePath); + List list = this.listByBean(where); + if (CollUtil.isNotEmpty(list)) { + List collect = list.stream() + .filter(staticFileStorageModel -> { + if (staticFileStorageModel.type() == 1) { + return true; + } + // 删除子目录 + this.delete(staticFileStorageModel.getId(), staticFileStorageModel.getAbsolutePath()); + return false; + }) + .map(BaseIdModel::getId) + .collect(Collectors.toList()); + this.update(Entity.create().set("status", 0), Entity.create().set("id", collect)); + } + } + } + } + + @Override + public void onOverflow(WatchEvent event, Path currentPath) { + log.error("事件丢失或者执行错误:{} {}", currentPath, event.context()); + } + + @Override + public void destroy() throws Exception { + this.closeWatchMonitor(); + } + + @Override + public String typeName() { + return getTableName(); + } + + /** + * 判断是否有权限操作 + * + * @param storageModel 静态文件 + */ + public void checkStaticDir(StaticFileStorageModel storageModel, String workspaceId) { + org.springframework.util.Assert.notNull(storageModel, "没有对应的文件信息"); + ServerWhitelist whitelistData = outGivingWhitelistService.getServerWhitelistData(workspaceId); + whitelistData.checkStaticDir(storageModel.getStaticDir()); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/func/openapi/controller/BaseDownloadApiController.java b/modules/server/src/main/java/org/dromara/jpom/func/openapi/controller/BaseDownloadApiController.java new file mode 100644 index 000000000..8a768231c --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/func/openapi/controller/BaseDownloadApiController.java @@ -0,0 +1,169 @@ +/* + * 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.func.openapi.controller; + +import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.date.DatePattern; +import cn.hutool.core.date.DateTime; +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.io.NioUtil; +import cn.hutool.core.util.*; +import cn.hutool.extra.servlet.ServletUtil; +import lombok.extern.slf4j.Slf4j; +import org.apache.catalina.connector.ClientAbortException; +import org.dromara.jpom.common.BaseJpomController; +import org.springframework.http.HttpHeaders; +import org.springframework.util.Assert; + +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.File; +import java.io.IOException; +import java.io.RandomAccessFile; +import java.nio.ByteBuffer; +import java.nio.channels.FileChannel; +import java.util.List; + +/** + * @author bwcx_jzy + * @since 23/12/28 028 + */ +@Slf4j +public abstract class BaseDownloadApiController extends BaseJpomController { + + protected long[] resolveRange(HttpServletRequest request, long fileSize, String id, String name, HttpServletResponse response) { + String range = ServletUtil.getHeader(request, HttpHeaders.RANGE, CharsetUtil.CHARSET_UTF_8); + log.debug("下载文件 {} {} {}", id, name, range); + long fromPos = 0, toPos, downloadSize; + if (StrUtil.isEmpty(range)) { + downloadSize = fileSize; + } else { + // 设置状态码 206 + response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); + List list = StrUtil.splitTrim(range, "="); + String rangeByte = CollUtil.getLast(list); + // Range: bytes=0-499 表示第 0-499 字节范围的内容 + // Range: bytes=500-999 表示第 500-999 字节范围的内容 + // Range: bytes=-500 表示最后 500 字节的内容 + // Range: bytes=500- 表示从第 500 字节开始到文件结束部分的内容 + // Range: bytes=0-0,-1 表示第一个和最后一个字节 + // Range: bytes=500-600,601-999 同时指定几个范围 + Assert.state(!StrUtil.contains(rangeByte, StrUtil.COMMA), "不支持分片多端下载"); + // TODO 解析更多格式的 RANGE 请求头 + long[] split = StrUtil.splitToLong(rangeByte, StrUtil.DASHED); + Assert.state(split != null, "range 传入的信息不正确"); + if (split.length == 2) { + // Range: bytes=0-499 表示第 0-499 字节范围的内容 + toPos = split[1]; + fromPos = split[0]; + } else if (split.length == 1) { + if (StrUtil.startWith(rangeByte, StrUtil.DASHED)) { + // Range: bytes=-500 表示最后 500 字节的内容 + fromPos = Math.max(fileSize - split[0], 0); + toPos = fileSize; + } else if (StrUtil.endWith(rangeByte, StrUtil.DASHED)) { + // Range: bytes=500- 表示从第 500 字节开始到文件结束部分的内容 + fromPos = split[0]; + toPos = fileSize; + } else { + throw new IllegalArgumentException("不支持的 range 格式 " + rangeByte); + } + } else { + throw new IllegalArgumentException("不支持的 range 格式 " + rangeByte); + } + downloadSize = toPos > fromPos ? (toPos - fromPos) : (fileSize - fromPos); + } + return new long[]{fromPos, downloadSize}; + } + + protected String convertName(String name1, String extName, String defaultName) { + // 需要考虑文件名中存在非法字符 + String name = ReUtil.replaceAll(name1, "[\\s\\\\/:\\*\\?\\\"<>\\|]", ""); + if (StrUtil.isEmpty(name)) { + name = defaultName; + } else if (!StrUtil.endWith(name, StrUtil.DOT + extName)) { + name += StrUtil.DOT + extName; + } + return name; + } + + public void download(File file, long fileSize, String name, long[] resolveRange, HttpServletResponse response) throws IOException { + Assert.state(FileUtil.isFile(file), "文件已经不存在啦"); + String contentType = ObjectUtil.defaultIfNull(FileUtil.getMimeType(name), "application/octet-stream"); + String charset = ObjectUtil.defaultIfNull(response.getCharacterEncoding(), CharsetUtil.UTF_8); + response.setHeader("Content-Disposition", StrUtil.format("attachment;filename=\"{}\"", + URLUtil.encode(name, CharsetUtil.charset(charset)))); + response.setContentType(contentType); + // 解析断点续传相关信息 + long fromPos = resolveRange[0]; + long downloadSize = resolveRange[1]; + // + response.setHeader(HttpHeaders.LAST_MODIFIED, DateTime.of(file.lastModified()).toString(DatePattern.HTTP_DATETIME_FORMAT)); + response.setHeader(HttpHeaders.ACCEPT_RANGES, "bytes"); + // Content-Range: bytes (unit first byte pos) - [last byte pos]/[entity legth] + response.setHeader(HttpHeaders.CONTENT_RANGE, StrUtil.format("bytes {}-{}/{}", fromPos, downloadSize, fileSize)); + response.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(downloadSize)); + // Copy the stream to the response's output stream. + ServletOutputStream out = null; + try (RandomAccessFile in = new RandomAccessFile(file, "r"); FileChannel channel = in.getChannel()) { + out = response.getOutputStream(); + // 设置下载起始位置 + if (fromPos > 0) { + channel.position(fromPos); + } + // 缓冲区大小 + int bufLen = (int) Math.min(downloadSize, IoUtil.DEFAULT_BUFFER_SIZE); + ByteBuffer buffer = ByteBuffer.allocate(bufLen); + int num; + long count = 0; + // 当前写到客户端的大小 + while ((num = channel.read(buffer)) != NioUtil.EOF) { + buffer.flip(); + out.write(buffer.array(), 0, num); + buffer.clear(); + count += num; + //处理最后一段,计算不满缓冲区的大小 + long last = (downloadSize - count); + if (last == 0) { + break; + } + if (last < bufLen) { + bufLen = (int) last; + buffer = ByteBuffer.allocate(bufLen); + } + } + response.flushBuffer(); + } catch (ClientAbortException clientAbortException) { + log.warn("客户端终止连接:{}", clientAbortException.getMessage()); + } catch (Exception e) { + log.error("数据下载失败", e); + if (out != null) { + out.write(StrUtil.bytes("error:" + e.getMessage())); + } + } finally { + IoUtil.close(out); + } + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/func/openapi/controller/FileStorageApiController.java b/modules/server/src/main/java/org/dromara/jpom/func/openapi/controller/FileStorageApiController.java index a01abdb97..e72575789 100644 --- a/modules/server/src/main/java/org/dromara/jpom/func/openapi/controller/FileStorageApiController.java +++ b/modules/server/src/main/java/org/dromara/jpom/func/openapi/controller/FileStorageApiController.java @@ -23,19 +23,15 @@ package org.dromara.jpom.func.openapi.controller; import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.date.DatePattern; -import cn.hutool.core.date.DateTime; import cn.hutool.core.io.FileUtil; -import cn.hutool.core.io.IoUtil; -import cn.hutool.core.io.NioUtil; import cn.hutool.core.lang.Opt; -import cn.hutool.core.util.*; +import cn.hutool.core.util.ObjectUtil; +import cn.hutool.core.util.ReflectUtil; +import cn.hutool.core.util.StrUtil; import cn.hutool.db.sql.Direction; import cn.hutool.db.sql.Order; import cn.hutool.extra.servlet.ServletUtil; import lombok.extern.slf4j.Slf4j; -import org.apache.catalina.connector.ClientAbortException; -import org.dromara.jpom.common.BaseJpomController; import org.dromara.jpom.common.ServerOpenApi; import org.dromara.jpom.common.interceptor.NotLogin; import org.dromara.jpom.func.files.model.FileStorageModel; @@ -43,21 +39,16 @@ import org.dromara.jpom.func.files.service.FileStorageService; import org.dromara.jpom.model.user.UserModel; import org.dromara.jpom.service.user.TriggerTokenLogServer; import org.dromara.jpom.system.ServerConfig; -import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.util.Assert; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RestController; -import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import java.io.File; import java.io.IOException; -import java.io.RandomAccessFile; -import java.nio.ByteBuffer; -import java.nio.channels.FileChannel; import java.util.Collections; import java.util.List; import java.util.Map; @@ -70,7 +61,7 @@ import java.util.stream.Collectors; @RestController @NotLogin @Slf4j -public class FileStorageApiController extends BaseJpomController { +public class FileStorageApiController extends BaseDownloadApiController { private final TriggerTokenLogServer triggerTokenLogServer; private final FileStorageService fileStorageService; @@ -84,49 +75,6 @@ public class FileStorageApiController extends BaseJpomController { this.serverConfig = serverConfig; } - private long[] resolveRange(HttpServletRequest request, long fileSize, String id, String name, HttpServletResponse response) { - String range = ServletUtil.getHeader(request, HttpHeaders.RANGE, CharsetUtil.CHARSET_UTF_8); - log.debug("下载文件 {} {} {}", id, name, range); - long fromPos = 0, toPos, downloadSize; - if (StrUtil.isEmpty(range)) { - downloadSize = fileSize; - } else { - response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT); - List list = StrUtil.splitTrim(range, "="); - String rangeByte = CollUtil.getLast(list); - // Range: bytes=0-499 表示第 0-499 字节范围的内容 - // Range: bytes=500-999 表示第 500-999 字节范围的内容 - // Range: bytes=-500 表示最后 500 字节的内容 - // Range: bytes=500- 表示从第 500 字节开始到文件结束部分的内容 - // Range: bytes=0-0,-1 表示第一个和最后一个字节 - // Range: bytes=500-600,601-999 同时指定几个范围 - Assert.state(!StrUtil.contains(rangeByte, StrUtil.COMMA), "不支持分片多端下载"); - // TODO 解析更多格式的 RANGE 请求头 - long[] split = StrUtil.splitToLong(rangeByte, StrUtil.DASHED); - Assert.state(split != null, "range 传入的信息不正确"); - if (split.length == 2) { - // Range: bytes=0-499 表示第 0-499 字节范围的内容 - toPos = split[1]; - fromPos = split[0]; - } else if (split.length == 1) { - if (StrUtil.startWith(rangeByte, StrUtil.DASHED)) { - // Range: bytes=-500 表示最后 500 字节的内容 - fromPos = Math.max(fileSize - split[0], 0); - toPos = fileSize; - } else if (StrUtil.endWith(rangeByte, StrUtil.DASHED)) { - // Range: bytes=500- 表示从第 500 字节开始到文件结束部分的内容 - fromPos = split[0]; - toPos = fileSize; - } else { - throw new IllegalArgumentException("不支持的 range 格式 " + rangeByte); - } - } else { - throw new IllegalArgumentException("不支持的 range 格式 " + rangeByte); - } - downloadSize = toPos > fromPos ? (toPos - fromPos) : (fileSize - fromPos); - } - return new long[]{fromPos, downloadSize}; - } /** * 解析别名参数 @@ -146,19 +94,19 @@ public class FileStorageApiController extends BaseJpomController { Assert.state(fileStorageService.exists(data), "别名或者token错误,或者已经失效"); } List orders = Opt.ofBlankAble(sort) - .map(s -> StrUtil.splitTrim(s, StrUtil.COMMA)) - .map(strings -> - strings.stream() - .map(s -> { - List list = StrUtil.splitTrim(s, StrUtil.COLON); - Order order = new Order(); - order.setField(CollUtil.getFirst(list)); - String s1 = CollUtil.get(list, 1); - order.setDirection(ObjectUtil.defaultIfNull(Direction.fromString(s1), Direction.DESC)); - return order; - }) - .collect(Collectors.toList())) - .orElse(Collections.emptyList()); + .map(s -> StrUtil.splitTrim(s, StrUtil.COMMA)) + .map(strings -> + strings.stream() + .map(s -> { + List list = StrUtil.splitTrim(s, StrUtil.COLON); + Order order = new Order(); + order.setField(CollUtil.getFirst(list)); + String s1 = CollUtil.get(list, 1); + order.setDirection(ObjectUtil.defaultIfNull(Direction.fromString(s1), Direction.DESC)); + return order; + }) + .collect(Collectors.toList())) + .orElse(Collections.emptyList()); Map paramMap = ServletUtil.getParamMap(request); FileStorageModel where = new FileStorageModel(); for (Map.Entry entry : paramMap.entrySet()) { @@ -194,69 +142,11 @@ public class FileStorageApiController extends BaseJpomController { // File storageSavePath = serverConfig.fileStorageSavePath(); File fileStorageFile = FileUtil.file(storageSavePath, storageModel.getPath()); - Assert.state(FileUtil.isFile(fileStorageFile), "文件已经不存在啦"); - long fileSize = FileUtil.size(fileStorageFile); // 需要考虑文件名中存在非法字符 - String name = ReUtil.replaceAll(storageModel.getName(), "[\\s\\\\/:\\*\\?\\\"<>\\|]", ""); - if (StrUtil.isEmpty(name)) { - name = fileStorageFile.getName(); - } else if (!StrUtil.endWith(name, StrUtil.DOT + storageModel.getExtName())) { - name += StrUtil.DOT + storageModel.getExtName(); - } - String contentType = ObjectUtil.defaultIfNull(FileUtil.getMimeType(name), "application/octet-stream"); - String charset = ObjectUtil.defaultIfNull(response.getCharacterEncoding(), CharsetUtil.UTF_8); - response.setHeader("Content-Disposition", StrUtil.format("attachment;filename=\"{}\"", - URLUtil.encode(name, CharsetUtil.charset(charset)))); - response.setContentType(contentType); + String name = this.convertName(storageModel.getName(), storageModel.getExtName(), fileStorageFile.getName()); // 解析断点续传相关信息 + long fileSize = FileUtil.size(fileStorageFile); long[] resolveRange = this.resolveRange(request, fileSize, storageModel.getId(), storageModel.getName(), response); - long fromPos = resolveRange[0]; - long downloadSize = resolveRange[1]; - // - response.setHeader(HttpHeaders.LAST_MODIFIED, DateTime.of(fileStorageFile.lastModified()).toString(DatePattern.HTTP_DATETIME_FORMAT)); - response.setHeader(HttpHeaders.ACCEPT_RANGES, "bytes"); - // Content-Range: bytes (unit first byte pos) - [last byte pos]/[entity legth] - response.setHeader(HttpHeaders.CONTENT_RANGE, StrUtil.format("bytes {}-{}/{}", fromPos, downloadSize, fileSize)); - response.setHeader(HttpHeaders.CONTENT_LENGTH, String.valueOf(downloadSize)); - // Copy the stream to the response's output stream. - ServletOutputStream out = null; - try (RandomAccessFile in = new RandomAccessFile(fileStorageFile, "r"); FileChannel channel = in.getChannel()) { - out = response.getOutputStream(); - // 设置下载起始位置 - if (fromPos > 0) { - channel.position(fromPos); - } - // 缓冲区大小 - int bufLen = (int) Math.min(downloadSize, IoUtil.DEFAULT_BUFFER_SIZE); - ByteBuffer buffer = ByteBuffer.allocate(bufLen); - int num; - long count = 0; - // 当前写到客户端的大小 - while ((num = channel.read(buffer)) != NioUtil.EOF) { - buffer.flip(); - out.write(buffer.array(), 0, num); - buffer.clear(); - count += num; - //处理最后一段,计算不满缓冲区的大小 - long last = (downloadSize - count); - if (last == 0) { - break; - } - if (last < bufLen) { - bufLen = (int) last; - buffer = ByteBuffer.allocate(bufLen); - } - } - response.flushBuffer(); - } catch (ClientAbortException clientAbortException) { - log.warn("客户端终止连接:{}", clientAbortException.getMessage()); - } catch (Exception e) { - log.error("数据下载失败", e); - if (out != null) { - out.write(StrUtil.bytes("error:" + e.getMessage())); - } - } finally { - IoUtil.close(out); - } + this.download(fileStorageFile, fileSize, name, resolveRange, response); } } diff --git a/modules/server/src/main/java/org/dromara/jpom/func/openapi/controller/NodeScriptTriggerApiController.java b/modules/server/src/main/java/org/dromara/jpom/func/openapi/controller/NodeScriptTriggerApiController.java index 10b61376d..9c831da47 100644 --- a/modules/server/src/main/java/org/dromara/jpom/func/openapi/controller/NodeScriptTriggerApiController.java +++ b/modules/server/src/main/java/org/dromara/jpom/func/openapi/controller/NodeScriptTriggerApiController.java @@ -82,7 +82,7 @@ public class NodeScriptTriggerApiController extends BaseJpomController { * @return json */ @RequestMapping(value = ServerOpenApi.NODE_SCRIPT_TRIGGER_URL, produces = MediaType.APPLICATION_JSON_VALUE) - public IJsonMessage trigger2(@PathVariable String id, @PathVariable String token) { + public IJsonMessage trigger2(@PathVariable String id, @PathVariable String token, HttpServletRequest request) { NodeScriptCacheModel item = nodeScriptServer.getByKey(id); Assert.notNull(item, "没有对应数据"); Assert.state(StrUtil.equals(token, item.getTriggerToken()), "触发token错误,或者已经失效"); @@ -95,7 +95,7 @@ public class NodeScriptTriggerApiController extends BaseJpomController { NodeModel nodeModel = nodeService.getByKey(item.getNodeId()); JSONObject reqData = new JSONObject(); reqData.put("id", item.getScriptId()); - reqData.put("params", JSONObject.toJSONString(ServletUtil.getParamMap(getRequest()))); + reqData.put("params", JSONObject.toJSONString(ServletUtil.getParamMap(request))); JsonMessage jsonMessage = NodeForward.request(nodeModel, NodeUrl.SCRIPT_EXEC, reqData); // JSONObject jsonObject = new JSONObject(); diff --git a/modules/server/src/main/java/org/dromara/jpom/func/openapi/controller/ProjectTriggerApiController.java b/modules/server/src/main/java/org/dromara/jpom/func/openapi/controller/ProjectTriggerApiController.java index 975790921..4a32461a0 100644 --- a/modules/server/src/main/java/org/dromara/jpom/func/openapi/controller/ProjectTriggerApiController.java +++ b/modules/server/src/main/java/org/dromara/jpom/func/openapi/controller/ProjectTriggerApiController.java @@ -77,13 +77,16 @@ public class ProjectTriggerApiController extends BaseJpomController { private NodeUrl resolveAction(String action) { if (StrUtil.equalsIgnoreCase(action, "stop")) { - return NodeUrl.Manage_Stop; + return NodeUrl.Manage_Operate; } if (StrUtil.equalsIgnoreCase(action, "start")) { - return NodeUrl.Manage_Start; + return NodeUrl.Manage_Operate; } if (StrUtil.equalsIgnoreCase(action, "restart")) { - return NodeUrl.Manage_Restart; + return NodeUrl.Manage_Operate; + } + if (StrUtil.equalsIgnoreCase(action, "reload")) { + return NodeUrl.Manage_Operate; } return NodeUrl.Manage_GetProjectStatus; } @@ -93,7 +96,7 @@ public class ProjectTriggerApiController extends BaseJpomController { // NodeModel nodeModel = nodeService.getByKey(item.getNodeId()); return NodeForward.request(nodeModel, resolveAction, - "id", item.getProjectId(), "getCopy", "true"); + "id", item.getProjectId(), "opt", action); } /** diff --git a/modules/server/src/main/java/org/dromara/jpom/func/openapi/controller/ServerScriptTriggerApiController.java b/modules/server/src/main/java/org/dromara/jpom/func/openapi/controller/ServerScriptTriggerApiController.java index 39c6c2eb5..25ec802ba 100644 --- a/modules/server/src/main/java/org/dromara/jpom/func/openapi/controller/ServerScriptTriggerApiController.java +++ b/modules/server/src/main/java/org/dromara/jpom/func/openapi/controller/ServerScriptTriggerApiController.java @@ -84,7 +84,7 @@ public class ServerScriptTriggerApiController extends BaseJpomController { * @return json */ @RequestMapping(value = ServerOpenApi.SERVER_SCRIPT_TRIGGER_URL, produces = MediaType.APPLICATION_JSON_VALUE) - public IJsonMessage trigger2(@PathVariable String id, @PathVariable String token) { + public IJsonMessage trigger2(@PathVariable String id, @PathVariable String token, HttpServletRequest request) { ScriptModel item = scriptServer.getByKey(id); Assert.notNull(item, "没有对应数据"); Assert.state(StrUtil.equals(token, item.getTriggerToken()), "触发token错误,或者已经失效"); @@ -96,7 +96,7 @@ public class ServerScriptTriggerApiController extends BaseJpomController { try { BaseServerController.resetInfo(userModel); // 解析参数 - Map paramMap = ServletUtil.getParamMap(getRequest()); + Map paramMap = ServletUtil.getParamMap(request); Map newParamMap = new HashMap<>(10); for (Map.Entry entry : paramMap.entrySet()) { String key = StrUtil.format("trigger_{}", entry.getKey()); diff --git a/modules/server/src/main/java/org/dromara/jpom/func/openapi/controller/SshCommandTriggerApiController.java b/modules/server/src/main/java/org/dromara/jpom/func/openapi/controller/SshCommandTriggerApiController.java index b86ee51cd..ece103ab8 100644 --- a/modules/server/src/main/java/org/dromara/jpom/func/openapi/controller/SshCommandTriggerApiController.java +++ b/modules/server/src/main/java/org/dromara/jpom/func/openapi/controller/SshCommandTriggerApiController.java @@ -35,7 +35,7 @@ import org.dromara.jpom.common.ServerOpenApi; import org.dromara.jpom.common.interceptor.NotLogin; import org.dromara.jpom.model.data.CommandModel; import org.dromara.jpom.model.user.UserModel; -import org.dromara.jpom.service.node.ssh.CommandService; +import org.dromara.jpom.service.node.ssh.SshCommandService; import org.dromara.jpom.service.user.TriggerTokenLogServer; import org.springframework.http.MediaType; import org.springframework.util.Assert; @@ -59,12 +59,12 @@ import java.util.stream.Collectors; @Slf4j public class SshCommandTriggerApiController extends BaseJpomController { - private final CommandService commandService; + private final SshCommandService sshCommandService; private final TriggerTokenLogServer triggerTokenLogServer; - public SshCommandTriggerApiController(CommandService commandService, + public SshCommandTriggerApiController(SshCommandService sshCommandService, TriggerTokenLogServer triggerTokenLogServer) { - this.commandService = commandService; + this.sshCommandService = sshCommandService; this.triggerTokenLogServer = triggerTokenLogServer; } @@ -77,19 +77,19 @@ public class SshCommandTriggerApiController extends BaseJpomController { */ @RequestMapping(value = ServerOpenApi.SSH_COMMAND_TRIGGER_URL, produces = MediaType.APPLICATION_JSON_VALUE) public IJsonMessage trigger2(@PathVariable String id, @PathVariable String token) { - CommandModel item = commandService.getByKey(id); + CommandModel item = sshCommandService.getByKey(id); Assert.notNull(item, "没有对应数据"); Assert.state(StrUtil.equals(token, item.getTriggerToken()), "触发token错误,或者已经失效"); // Assert.hasText(item.getSshIds(), "当前脚本未绑定 SSH 节点,不能使用触发器执行"); - UserModel userModel = triggerTokenLogServer.getUserByToken(token, commandService.typeName()); + UserModel userModel = triggerTokenLogServer.getUserByToken(token, sshCommandService.typeName()); // Assert.notNull(userModel, "触发token错误,或者已经失效:-1"); String batchId; try { BaseServerController.resetInfo(userModel); - batchId = commandService.executeBatch(item, item.getDefParams(), item.getSshIds(), 2); + batchId = sshCommandService.executeBatch(item, item.getDefParams(), item.getSshIds(), 2); } catch (Exception e) { log.error("触发自动执行SSH命令模版异常", e); return new JsonMessage<>(500, "执行异常:" + e.getMessage()); @@ -130,12 +130,12 @@ public class SshCommandTriggerApiController extends BaseJpomController { JSONObject jsonObject = (JSONObject) o; String id = jsonObject.getString("id"); String token = jsonObject.getString("token"); - CommandModel item = commandService.getByKey(id); + CommandModel item = sshCommandService.getByKey(id); if (item == null) { jsonObject.put("msg", "没有对应数据"); return; } - UserModel userModel = triggerTokenLogServer.getUserByToken(token, commandService.typeName()); + UserModel userModel = triggerTokenLogServer.getUserByToken(token, sshCommandService.typeName()); if (userModel == null) { jsonObject.put("msg", "对应的用户不存在,触发器已失效"); return; @@ -148,7 +148,7 @@ public class SshCommandTriggerApiController extends BaseJpomController { BaseServerController.resetInfo(userModel); String batchId = null; try { - batchId = commandService.executeBatch(item, item.getDefParams(), item.getSshIds(), 2); + batchId = sshCommandService.executeBatch(item, item.getDefParams(), item.getSshIds(), 2); } catch (Exception e) { log.error("触发自动执行命令模版异常", e); jsonObject.put("msg", "执行异常:" + e.getMessage()); diff --git a/modules/server/src/main/java/org/dromara/jpom/func/openapi/controller/StaticFileStorageApiController.java b/modules/server/src/main/java/org/dromara/jpom/func/openapi/controller/StaticFileStorageApiController.java new file mode 100644 index 000000000..6be07ba18 --- /dev/null +++ b/modules/server/src/main/java/org/dromara/jpom/func/openapi/controller/StaticFileStorageApiController.java @@ -0,0 +1,85 @@ +/* + * 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.func.openapi.controller; + +import cn.hutool.core.io.FileUtil; +import cn.hutool.core.util.StrUtil; +import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.common.ServerOpenApi; +import org.dromara.jpom.common.interceptor.NotLogin; +import org.dromara.jpom.func.files.model.StaticFileStorageModel; +import org.dromara.jpom.func.files.service.StaticFileStorageService; +import org.dromara.jpom.model.user.UserModel; +import org.dromara.jpom.service.user.TriggerTokenLogServer; +import org.springframework.http.MediaType; +import org.springframework.util.Assert; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.File; +import java.io.IOException; + +/** + * @author bwcx_jzy + * @since 2023/12/28 + */ +@RestController +@NotLogin +@Slf4j +public class StaticFileStorageApiController extends BaseDownloadApiController { + + private final TriggerTokenLogServer triggerTokenLogServer; + private final StaticFileStorageService staticFileStorageService; + + public StaticFileStorageApiController(TriggerTokenLogServer triggerTokenLogServer, + StaticFileStorageService staticFileStorageService) { + this.triggerTokenLogServer = triggerTokenLogServer; + this.staticFileStorageService = staticFileStorageService; + } + + + @GetMapping(value = ServerOpenApi.STATIC_FILE_STORAGE_DOWNLOAD, produces = MediaType.APPLICATION_JSON_VALUE) + public void download(@PathVariable String id, + @PathVariable String token, + HttpServletRequest request, + HttpServletResponse response) throws IOException { + StaticFileStorageModel storageModel = staticFileStorageService.getByKey(id); + Assert.notNull(storageModel, "文件不存在"); + + Assert.state(StrUtil.equals(token, storageModel.getTriggerToken()), "token错误,或者已经失效"); + // + UserModel userModel = triggerTokenLogServer.getUserByToken(token, staticFileStorageService.typeName()); + // + Assert.notNull(userModel, "token错误,或者已经失效:-1"); + File file = FileUtil.file(storageModel.getAbsolutePath()); + // 需要考虑文件名中存在非法字符 + String name = this.convertName(storageModel.getName(), storageModel.getExtName(), file.getName()); + // 解析断点续传相关信息 + long fileSize = FileUtil.size(file); + long[] resolveRange = this.resolveRange(request, fileSize, storageModel.getId(), storageModel.getName(), response); + this.download(file, fileSize, name, resolveRange, response); + } +} diff --git a/modules/server/src/main/java/org/dromara/jpom/model/data/BuildInfoModel.java b/modules/server/src/main/java/org/dromara/jpom/model/data/BuildInfoModel.java index f89d37382..9e82cd44e 100644 --- a/modules/server/src/main/java/org/dromara/jpom/model/data/BuildInfoModel.java +++ b/modules/server/src/main/java/org/dromara/jpom/model/data/BuildInfoModel.java @@ -23,7 +23,6 @@ package org.dromara.jpom.model.data; import cn.hutool.core.annotation.PropIgnore; -import cn.hutool.core.util.ObjectUtil; import lombok.Builder; import lombok.Data; import lombok.EqualsAndHashCode; @@ -147,10 +146,6 @@ public class BuildInfoModel extends BaseGroupModel { */ private Integer resultKeepDay; - public Integer getResultKeepDay() { - return ObjectUtil.defaultIfNull(this.resultKeepDay, 0); - } - @Tolerate public BuildInfoModel() { } diff --git a/modules/server/src/main/java/org/dromara/jpom/model/data/NodeModel.java b/modules/server/src/main/java/org/dromara/jpom/model/data/NodeModel.java index 8c0766c9c..deb448fd4 100644 --- a/modules/server/src/main/java/org/dromara/jpom/model/data/NodeModel.java +++ b/modules/server/src/main/java/org/dromara/jpom/model/data/NodeModel.java @@ -34,8 +34,8 @@ import org.dromara.jpom.model.BaseMachineModel; * 节点实体 * * @author bwcx_jzy - * @since 2019/4/16 * @see MachineNodeModel + * @since 2019/4/16 */ @EqualsAndHashCode(callSuper = true) @TableName(value = "NODE_INFO", name = "节点信息") @@ -90,6 +90,14 @@ public class NodeModel extends BaseMachineModel { @PropIgnore private WorkspaceModel workspace; + /** + * jpom 项目数 + */ + private Integer jpomProjectCount; + /** + * jpom 脚本数据 + */ + private Integer jpomScriptCount; public boolean isOpenStatus() { return openStatus != null && openStatus == 1; diff --git a/modules/server/src/main/java/org/dromara/jpom/model/data/ServerWhitelist.java b/modules/server/src/main/java/org/dromara/jpom/model/data/ServerWhitelist.java index 6ef631f16..322c6498a 100644 --- a/modules/server/src/main/java/org/dromara/jpom/model/data/ServerWhitelist.java +++ b/modules/server/src/main/java/org/dromara/jpom/model/data/ServerWhitelist.java @@ -23,18 +23,21 @@ package org.dromara.jpom.model.data; import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.io.FileUtil; import cn.hutool.core.util.StrUtil; import cn.keepbx.jpom.model.BaseJsonModel; import lombok.Data; import lombok.EqualsAndHashCode; import org.springframework.util.Assert; +import java.io.File; +import java.util.ArrayList; import java.util.List; import java.util.Set; import java.util.stream.Collectors; /** - * 节点分发白名单 + * 节点分发授权 * * @author bwcx_jzy * @since 2019/4/22 @@ -56,7 +59,7 @@ public class ServerWhitelist extends BaseJsonModel { } /** - * 项目的白名单 + * 项目的授权 */ private List outGiving; @@ -65,13 +68,47 @@ public class ServerWhitelist extends BaseJsonModel { */ private Set allowRemoteDownloadHost; - public List outGiving() { - return AgentWhitelist.useConvert(outGiving); + /** + * 静态目录 + */ + private List staticDir; + + /** + * 规范化路径 + * + * @return list + */ + public List staticDir() { + if (staticDir == null) { + return new ArrayList<>(); + } + return staticDir.stream() + .map(s -> { + // 规范化 + File file = FileUtil.file(s); + String absolutePath = file.getAbsolutePath(); + return FileUtil.normalize(absolutePath); + }) + .collect(Collectors.toList()); } + /** + * 验证静态目录权限 + */ + public void checkStaticDir(String path) { + List dir = this.staticDir; + boolean contains = CollUtil.contains(dir, path); + Assert.state(contains, "没有当前静态目录权限"); + } + + /** + * 判断指定 url 是否在授权范围 + * + * @param url url 地址 + */ public void checkAllowRemoteDownloadHost(String url) { Set allowRemoteDownloadHost = this.getAllowRemoteDownloadHost(); - Assert.state(CollUtil.isNotEmpty(allowRemoteDownloadHost), "还没有配置运行的远程地址"); + Assert.state(CollUtil.isNotEmpty(allowRemoteDownloadHost), "还没有配置允许的远程地址"); List collect = allowRemoteDownloadHost.stream() .filter(s -> StrUtil.startWith(url, s)) .collect(Collectors.toList()); diff --git a/modules/server/src/main/java/org/dromara/jpom/model/data/SystemIpConfigModel.java b/modules/server/src/main/java/org/dromara/jpom/model/data/SystemIpConfigModel.java index 53b5a17a2..75ef7fda7 100644 --- a/modules/server/src/main/java/org/dromara/jpom/model/data/SystemIpConfigModel.java +++ b/modules/server/src/main/java/org/dromara/jpom/model/data/SystemIpConfigModel.java @@ -33,7 +33,7 @@ public class SystemIpConfigModel extends BaseJsonModel { public static final String ID = "IP_CONFIG"; /** - * ip 白名单 允许访问 + * ip 授权 允许访问 */ private String allowed; diff --git a/modules/server/src/main/java/org/dromara/jpom/model/log/BuildHistoryLog.java b/modules/server/src/main/java/org/dromara/jpom/model/log/BuildHistoryLog.java index 0c6828b0c..97cfe4b75 100644 --- a/modules/server/src/main/java/org/dromara/jpom/model/log/BuildHistoryLog.java +++ b/modules/server/src/main/java/org/dromara/jpom/model/log/BuildHistoryLog.java @@ -78,13 +78,6 @@ public class BuildHistoryLog extends BaseWorkspaceModel { * 构建名称 */ private String buildName; - /** - * 构建名称-未用 - * - * @see this#buildName - */ - @Deprecated - private String name; /** * 构建编号 * diff --git a/modules/server/src/main/java/org/dromara/jpom/model/log/OutGivingLog.java b/modules/server/src/main/java/org/dromara/jpom/model/log/OutGivingLog.java index 0e8169ce3..4c78ece72 100644 --- a/modules/server/src/main/java/org/dromara/jpom/model/log/OutGivingLog.java +++ b/modules/server/src/main/java/org/dromara/jpom/model/log/OutGivingLog.java @@ -79,4 +79,16 @@ public class OutGivingLog extends BaseWorkspaceModel { * 进度信息 */ private Long progressSize; + /** + * 分发方式 + * upload: "手动上传", + * download: "远程下载", + * "build-trigger": "构建触发", + * "use-build": "构建产物", + */ + private String mode; + /** + * 数据 + */ + private String modeData; } diff --git a/modules/server/src/main/java/org/dromara/jpom/model/node/ProjectInfoCacheModel.java b/modules/server/src/main/java/org/dromara/jpom/model/node/ProjectInfoCacheModel.java index 5ecdf3283..e873cd27b 100644 --- a/modules/server/src/main/java/org/dromara/jpom/model/node/ProjectInfoCacheModel.java +++ b/modules/server/src/main/java/org/dromara/jpom/model/node/ProjectInfoCacheModel.java @@ -47,7 +47,7 @@ public class ProjectInfoCacheModel extends BaseNodeGroupModel { private String mainClass; private String lib; /** - * 白名单目录 + * 授权目录 */ private String whitelistDirectory; /** diff --git a/modules/server/src/main/java/org/dromara/jpom/model/outgiving/OutGivingModel.java b/modules/server/src/main/java/org/dromara/jpom/model/outgiving/OutGivingModel.java index 26906714d..3e41fdf17 100644 --- a/modules/server/src/main/java/org/dromara/jpom/model/outgiving/OutGivingModel.java +++ b/modules/server/src/main/java/org/dromara/jpom/model/outgiving/OutGivingModel.java @@ -109,6 +109,15 @@ public class OutGivingModel extends BaseGroupModel { * 构建发布状态通知 */ private String webhook; + /** + * 分发方式 + * upload: "手动上传", + * download: "远程下载", + * "build-trigger": "构建触发", + * "use-build": "构建产物", + */ + private String mode; + private String modeData; public boolean clearOld() { return clearOld != null && clearOld; diff --git a/modules/server/src/main/java/org/dromara/jpom/model/script/ScriptModel.java b/modules/server/src/main/java/org/dromara/jpom/model/script/ScriptModel.java index 18e91321a..ede3ffd84 100644 --- a/modules/server/src/main/java/org/dromara/jpom/model/script/ScriptModel.java +++ b/modules/server/src/main/java/org/dromara/jpom/model/script/ScriptModel.java @@ -23,6 +23,7 @@ package org.dromara.jpom.model.script; import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IoUtil; import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.StrUtil; import lombok.Data; @@ -37,6 +38,7 @@ import org.dromara.jpom.util.CommandUtil; import org.dromara.jpom.util.FileUtils; import java.io.File; +import java.io.InputStream; /** * @author bwcx_jzy @@ -104,15 +106,19 @@ public class ScriptModel extends BaseWorkspaceModel { return FileUtil.file(path, "log", executeId + ".log"); } + /** + * 加载脚本文件 + * + * @return file + */ public File scriptFile() { + InputStream templateInputStream = ExtConfigBean.getConfigResourceInputStream("/exec/template." + CommandUtil.SUFFIX); + String defaultTemplate = IoUtil.readUtf8(templateInputStream); + String dataPath = JpomApplication.getInstance().getDataPath(); File scriptFile = FileUtil.file(dataPath, Const.SCRIPT_RUN_CACHE_DIRECTORY, StrUtil.format("{}.{}", IdUtil.fastSimpleUUID(), CommandUtil.SUFFIX)); - FileUtils.writeScript(this.getContext(), scriptFile, ExtConfigBean.getConsoleLogCharset()); -// File path = this.scriptPath(); -// File file = FileUtil.file(path, StrUtil.format("script.{}", CommandUtil.SUFFIX)); -// // -// FileUtil.writeString(getContext(), file, ExtConfigBean.getConsoleLogCharset()); + FileUtils.writeScript(defaultTemplate + this.getContext(), scriptFile, ExtConfigBean.getConsoleLogCharset()); return scriptFile; } diff --git a/modules/server/src/main/java/org/dromara/jpom/monitor/MonitorItem.java b/modules/server/src/main/java/org/dromara/jpom/monitor/MonitorItem.java index 725cbe7ba..11e1b67ef 100644 --- a/modules/server/src/main/java/org/dromara/jpom/monitor/MonitorItem.java +++ b/modules/server/src/main/java/org/dromara/jpom/monitor/MonitorItem.java @@ -49,6 +49,7 @@ import org.dromara.jpom.service.monitor.MonitorService; import org.dromara.jpom.service.node.NodeService; import org.dromara.jpom.service.node.ProjectInfoCacheService; import org.dromara.jpom.service.user.UserService; +import org.dromara.jpom.webhook.DefaultWebhookPluginImpl; import java.util.HashMap; import java.util.List; @@ -118,7 +119,7 @@ public class MonitorItem implements Task { String context; try { //查询项目运行状态 - JsonMessage jsonMessage = NodeForward.request(nodeModel, NodeUrl.Manage_GetProjectStatus, "id", id, "getCopy", true); + JsonMessage jsonMessage = NodeForward.request(nodeModel, NodeUrl.Manage_GetProjectStatus, "id", id); if (jsonMessage.success()) { JSONObject jsonObject = jsonMessage.getData(); int pid = jsonObject.getIntValue("pId"); @@ -195,7 +196,7 @@ public class MonitorItem implements Task { if (monitorModel.autoRestart()) { // 执行重启 try { - JsonMessage reJson = NodeForward.request(nodeModel, NodeUrl.Manage_Restart, "id", id); + JsonMessage reJson = NodeForward.request(nodeModel, NodeUrl.Manage_Operate, "id", id, "opt", "restart"); if (reJson.success()) { // 重启成功 runStatus = true; @@ -272,6 +273,7 @@ public class MonitorItem implements Task { } IPlugin plugin = PluginFactory.getPlugin("webhook"); Map map = new HashMap<>(10); + map.put("JPOM_WEBHOOK_EVENT", DefaultWebhookPluginImpl.WebhookEvent.MONITOR); map.put("monitorId", monitorModel.getId()); map.put("monitorName", monitorModel.getName()); map.put("nodeId", monitorNotifyLog.getNodeId()); diff --git a/modules/server/src/main/java/org/dromara/jpom/outgiving/OutGivingRun.java b/modules/server/src/main/java/org/dromara/jpom/outgiving/OutGivingRun.java index eed632a8b..d650e4837 100644 --- a/modules/server/src/main/java/org/dromara/jpom/outgiving/OutGivingRun.java +++ b/modules/server/src/main/java/org/dromara/jpom/outgiving/OutGivingRun.java @@ -55,6 +55,7 @@ import org.dromara.jpom.service.outgiving.OutGivingServer; import org.dromara.jpom.util.LogRecorder; import org.dromara.jpom.util.StrictSyncFinisher; import org.dromara.jpom.util.SyncFinisherUtil; +import org.dromara.jpom.webhook.DefaultWebhookPluginImpl; import org.springframework.util.Assert; import java.io.File; @@ -90,6 +91,15 @@ public class OutGivingRun { private UserModel userModel; private boolean unzip; private int stripComponents; + /** + * 分发方式 + * upload: "手动上传", + * download: "远程下载", + * "build-trigger": "构建触发", + * "use-build": "构建产物", + */ + private String mode; + private String modeData; /** * 是否删除发布文件 */ @@ -306,6 +316,9 @@ public class OutGivingRun { outGivingLog.setModifyUser(userId); outGivingLog.setStartTime(SystemClock.now()); outGivingLog.setStatus(OutGivingNodeProject.Status.Prepare.getCode()); + outGivingLog.setMode(mode); + // 限制最大长度 + outGivingLog.setModeData(StrUtil.maxLength(modeData, 400)); return outGivingLog; }) .collect(Collectors.toList()); @@ -355,6 +368,7 @@ public class OutGivingRun { map.put("executeTime", SystemClock.now()); try { IPlugin plugin = PluginFactory.getPlugin("webhook"); + map.put("JPOM_WEBHOOK_EVENT", DefaultWebhookPluginImpl.WebhookEvent.DISTRIBUTE); plugin.execute(webhook, map); } catch (Exception e) { log.error("WebHooks 调用错误", e); diff --git a/modules/server/src/main/java/org/dromara/jpom/permission/ClassFeature.java b/modules/server/src/main/java/org/dromara/jpom/permission/ClassFeature.java index c630ee091..a44e96bc3 100644 --- a/modules/server/src/main/java/org/dromara/jpom/permission/ClassFeature.java +++ b/modules/server/src/main/java/org/dromara/jpom/permission/ClassFeature.java @@ -29,6 +29,7 @@ import org.dromara.jpom.func.assets.server.MachineSshServer; import org.dromara.jpom.func.cert.service.CertificateInfoService; import org.dromara.jpom.func.files.service.FileReleaseTaskService; import org.dromara.jpom.func.files.service.FileStorageService; +import org.dromara.jpom.func.files.service.StaticFileStorageService; import org.dromara.jpom.func.system.service.ClusterInfoService; import org.dromara.jpom.func.user.server.UserLoginLogServer; import org.dromara.jpom.service.dblog.*; @@ -42,7 +43,7 @@ import org.dromara.jpom.service.node.ProjectInfoCacheService; import org.dromara.jpom.service.node.script.NodeScriptExecuteLogServer; import org.dromara.jpom.service.node.script.NodeScriptServer; import org.dromara.jpom.service.node.ssh.CommandExecLogService; -import org.dromara.jpom.service.node.ssh.CommandService; +import org.dromara.jpom.service.node.ssh.SshCommandService; import org.dromara.jpom.service.node.ssh.SshService; import org.dromara.jpom.service.outgiving.DbOutGivingLogService; import org.dromara.jpom.service.outgiving.LogReadServer; @@ -74,12 +75,12 @@ public enum ClassFeature { SSH_FILE("SSH文件管理", SshService.class), SSH_TERMINAL("SSH终端", SshService.class), SSH_TERMINAL_LOG("SSH终端日志", SshTerminalExecuteLogService.class), - SSH_COMMAND("SSH命令管理", CommandService.class), + SSH_COMMAND("SSH命令管理", SshCommandService.class), SSH_COMMAND_LOG("SSH命令日志", CommandExecLogService.class), OUTGIVING("分发管理", OutGivingServer.class), LOG_READ("日志阅读", LogReadServer.class), OUTGIVING_LOG("分发日志", DbOutGivingLogService.class), - OUTGIVING_CONFIG_WHITELIST("分发白名单配置"), + OUTGIVING_CONFIG_WHITELIST("授权配置"), MONITOR("项目监控", MonitorService.class), MONITOR_LOG("监控日志", DbMonitorNotifyLogService.class), OPT_MONITOR("操作监控", MonitorUserOptService.class), @@ -95,6 +96,7 @@ public enum ClassFeature { USER_LOG("操作日志", DbUserOperateLogService.class), USER_LOGIN_LOG("登录日志", UserLoginLogServer.class), FILE_STORAGE("文件存储中心", FileStorageService.class), + STATIC_FILE_STORAGE("静态文件存储", StaticFileStorageService.class), FILE_STORAGE_RELEASE("文件发布", FileReleaseTaskService.class), CERTIFICATE_INFO("证书管理", CertificateInfoService.class), USER_PERMISSION_GROUP("权限分组", UserPermissionGroupServer.class), @@ -108,9 +110,9 @@ public enum ClassFeature { SYSTEM_ASSETS_MACHINE_DOCKER("DOCKER资产管理", MachineDockerServer.class), SYSTEM_CONFIG("服务端系统配置"), SYSTEM_EXT_CONFIG("系统配置目录"), - SYSTEM_CONFIG_IP("系统配置IP白名单"), + SYSTEM_CONFIG_IP("系统配置IP授权"), // SYSTEM_CONFIG_MENUS("系统菜单配置"), - SYSTEM_NODE_WHITELIST("节点白名单分发"), + SYSTEM_NODE_WHITELIST("节点授权分发"), SYSTEM_BACKUP("数据库备份", BackupInfoService.class), SYSTEM_WORKSPACE("工作空间", WorkspaceService.class), SYSTEM_WORKSPACE_ENV("环境变量", WorkspaceEnvVarService.class), @@ -132,8 +134,8 @@ public enum ClassFeature { // TOMCAT_LOG("Tomcat log", ClassFeature.NODE), - NODE_CONFIG_WHITELIST("节点白名单配置", ClassFeature.NODE), - NODE_CONFIG("节点白名单配置", ClassFeature.NODE), + NODE_CONFIG_WHITELIST("节点授权配置", ClassFeature.NODE), + NODE_CONFIG("节点授权配置", ClassFeature.NODE), NODE_CACHE("节点缓存", ClassFeature.NODE), NODE_LOG("节点系统日志", ClassFeature.NODE), NODE_UPGRADE("节点在线升级", ClassFeature.NODE), diff --git a/modules/server/src/main/java/org/dromara/jpom/service/ITriggerToken.java b/modules/server/src/main/java/org/dromara/jpom/service/ITriggerToken.java index 70e54d3a0..8608bfee4 100644 --- a/modules/server/src/main/java/org/dromara/jpom/service/ITriggerToken.java +++ b/modules/server/src/main/java/org/dromara/jpom/service/ITriggerToken.java @@ -36,4 +36,13 @@ public interface ITriggerToken { * @return 数据分类名称 */ String typeName(); + + /** + * 判断是否存在 + * + * @param dataId 数据id + * @return true 存在 + */ + boolean exists(String dataId); + } diff --git a/modules/server/src/main/java/org/dromara/jpom/service/docker/DockerInfoService.java b/modules/server/src/main/java/org/dromara/jpom/service/docker/DockerInfoService.java index 92ba461c7..ebe4fa552 100644 --- a/modules/server/src/main/java/org/dromara/jpom/service/docker/DockerInfoService.java +++ b/modules/server/src/main/java/org/dromara/jpom/service/docker/DockerInfoService.java @@ -22,7 +22,9 @@ */ package org.dromara.jpom.service.docker; +import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.StrUtil; +import cn.hutool.db.Entity; import cn.hutool.db.sql.Condition; import lombok.extern.slf4j.Slf4j; import org.dromara.jpom.model.docker.DockerInfoModel; @@ -30,7 +32,11 @@ import org.dromara.jpom.service.h2db.BaseWorkspaceService; import org.springframework.stereotype.Service; import org.springframework.util.Assert; +import java.util.ArrayList; import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; /** @@ -87,6 +93,26 @@ public class DockerInfoService extends BaseWorkspaceService { return (int) super.count(sql, workspaceId, StrUtil.wrap(tag, StrUtil.COLON)); } + /** + * 根据 tag 查询 容器 + * + * @param workspaceId 工作空间 + * @return count + */ + public List allTag(String workspaceId) { + String sql = StrUtil.format("SELECT tags FROM {} where workspaceId=?", super.getTableName()); + List query = super.query(sql, workspaceId); + if (CollUtil.isEmpty(query)) { + return new ArrayList<>(); + } + return query.stream() + .map(entity -> entity.getStr("tags")) + .flatMap((Function>) s -> StrUtil.splitTrim(s, StrUtil.COLON).stream()) + .filter(StrUtil::isNotEmpty) + .distinct() + .collect(Collectors.toList()); + } + /** * 将节点信息同步到其他工作空间 * diff --git a/modules/server/src/main/java/org/dromara/jpom/service/h2db/BaseDbService.java b/modules/server/src/main/java/org/dromara/jpom/service/h2db/BaseDbService.java index accd6f9ce..a7ba0c337 100644 --- a/modules/server/src/main/java/org/dromara/jpom/service/h2db/BaseDbService.java +++ b/modules/server/src/main/java/org/dromara/jpom/service/h2db/BaseDbService.java @@ -285,15 +285,44 @@ public abstract class BaseDbService extends BaseDbCommonS } /** - * 根据主键生成 + * 同步 bean 删除 + * + * @param info bean + * @return 影响行数 + */ + public int delByBean(T info) { + Entity where = this.dataBeanToEntity(info); + Assert.state(!where.isEmpty(), "没有添加任何参数:-2"); + return this.del(where); + } + + + /** + * 根据主键删除 * * @param keyValue 主键值 * @return 影响行数 */ public int delByKey(String keyValue) { + if (StrUtil.isEmpty(keyValue)) { + return 0; + } return this.delByKey(keyValue, null); } + /** + * 根据主键删除 + * + * @param ids 主键值 + * @return 影响行数 + */ + public int delByKey(List ids) { + if (CollUtil.isEmpty(ids)) { + return 0; + } + return this.delByKey(ids, null); + } + /** * 根据主键生成 * @@ -309,7 +338,7 @@ public abstract class BaseDbService extends BaseDbCommonS if (consumer != null) { consumer.accept(where); } - Assert.state(where.size() > 0, "没有添加任何参数:-1"); + Assert.state(!where.isEmpty(), "没有添加任何参数:-1"); return del(where); } @@ -324,6 +353,19 @@ public abstract class BaseDbService extends BaseDbCommonS return this.exists(entity); } + /** + * 判断是否存在 + * + * @param dataId 数据id + * @return true 存在 + */ + public boolean exists(String dataId) { + Entity entity = Entity.create(); + entity.set(ID_STR, dataId); + long count = this.count(entity); + return count > 0; + } + /** * 判断是否存在 * diff --git a/modules/server/src/main/java/org/dromara/jpom/service/h2db/BaseGroupService.java b/modules/server/src/main/java/org/dromara/jpom/service/h2db/BaseGroupService.java deleted file mode 100644 index 04d8da6d4..000000000 --- a/modules/server/src/main/java/org/dromara/jpom/service/h2db/BaseGroupService.java +++ /dev/null @@ -1,94 +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. - */ -///* -// * 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.h2db; -// -//import cn.hutool.db.Entity; -//import org.dromara.jpom.common.Const; -//import org.dromara.jpom.common.ServerConst; -//import org.dromara.jpom.model.BaseGroupModel; -// -//import javax.servlet.http.HttpServletRequest; -//import java.util.List; -//import java.util.Objects; -//import java.util.stream.Collectors; -//import java.util.stream.Stream; -// -///** -// * 分组 server -// * -// * @author bwcx_jzy -// * @since 2022/1/5 -// */ -//public abstract class BaseGroupService extends BaseWorkspaceService { -// -// -// /** -// * load date group by group name -// * -// * @return list -// */ -// public List listGroup(HttpServletRequest request) { -// String workspaceId = getCheckUserWorkspace(request); -// String sql = "select `GROUP` from " + getTableName() + " where workspaceId=? group by `GROUP`"; -// List list = super.query(sql, workspaceId); -// // 筛选字段 -// return list.stream().flatMap(entity -> { -// Object obj = entity.get(ServerConst.GROUP_STR); -// if (obj == null) { -// return null; -// } -// return Stream.of(String.valueOf(obj)); -// }).filter(Objects::nonNull) -// .distinct().collect(Collectors.toList()); -// } -// -// /** -// * 恢复字段 -// */ -// public void repairGroupFiled() { -// String sql = "update " + getTableName() + " set `GROUP`=? where `GROUP` is null or `GROUP`=''"; -// super.execute(sql, Const.DEFAULT_GROUP_NAME); -// } -//} diff --git a/modules/server/src/main/java/org/dromara/jpom/service/h2db/BaseNodeGroupService.java b/modules/server/src/main/java/org/dromara/jpom/service/h2db/BaseNodeGroupService.java deleted file mode 100644 index 79192bc16..000000000 --- a/modules/server/src/main/java/org/dromara/jpom/service/h2db/BaseNodeGroupService.java +++ /dev/null @@ -1,99 +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. - */ -///* -// * 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.h2db; -// -//import cn.hutool.db.Entity; -//import org.dromara.jpom.common.Const; -//import org.dromara.jpom.common.ServerConst; -//import org.dromara.jpom.model.BaseNodeGroupModel; -//import org.dromara.jpom.service.node.NodeService; -//import org.dromara.jpom.service.system.WorkspaceService; -// -//import javax.servlet.http.HttpServletRequest; -//import java.util.List; -//import java.util.Objects; -//import java.util.stream.Collectors; -//import java.util.stream.Stream; -// -///** -// * @author bwcx_jzy -// * @since 2023/2/8 -// */ -//public abstract class BaseNodeGroupService extends BaseNodeService { -// -// protected BaseNodeGroupService(NodeService nodeService, -// WorkspaceService workspaceService, -// String dataName) { -// super(nodeService, workspaceService, dataName); -// } -// -// /** -// * load date group by group name -// * -// * @return list -// */ -// public List listGroup(HttpServletRequest request) { -// String workspaceId = getCheckUserWorkspace(request); -// String sql = "select `GROUP` from " + getTableName() + " where workspaceId=? group by `GROUP`"; -// List list = super.query(sql, workspaceId); -// // 筛选字段 -// return list.stream().flatMap(entity -> { -// Object obj = entity.get(ServerConst.GROUP_STR); -// if (obj == null) { -// return null; -// } -// return Stream.of(String.valueOf(obj)); -// }).filter(Objects::nonNull) -// .distinct().collect(Collectors.toList()); -// } -// -// /** -// * 恢复字段 -// */ -// public void repairGroupFiled() { -// String sql = "update " + getTableName() + " set `GROUP`=? where `GROUP` is null or `GROUP`=''"; -// super.execute(sql, Const.DEFAULT_GROUP_NAME); -// } -//} diff --git a/modules/server/src/main/java/org/dromara/jpom/service/h2db/BaseNodeService.java b/modules/server/src/main/java/org/dromara/jpom/service/h2db/BaseNodeService.java index 2f4a58cd5..eb20735e5 100644 --- a/modules/server/src/main/java/org/dromara/jpom/service/h2db/BaseNodeService.java +++ b/modules/server/src/main/java/org/dromara/jpom/service/h2db/BaseNodeService.java @@ -31,23 +31,21 @@ import cn.hutool.core.util.ReflectUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.db.Entity; import cn.hutool.extra.servlet.ServletUtil; -import cn.hutool.extra.spring.SpringUtil; import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; import lombok.extern.slf4j.Slf4j; import org.dromara.jpom.common.BaseServerController; import org.dromara.jpom.common.Const; import org.dromara.jpom.common.ServerConst; +import org.dromara.jpom.exception.AgentAuthorizeException; +import org.dromara.jpom.exception.AgentException; +import org.dromara.jpom.func.assets.model.MachineNodeModel; import org.dromara.jpom.model.BaseNodeModel; -import org.dromara.jpom.model.PageResultDto; import org.dromara.jpom.model.data.NodeModel; import org.dromara.jpom.model.data.WorkspaceModel; import org.dromara.jpom.model.user.UserModel; import org.dromara.jpom.service.node.NodeService; import org.dromara.jpom.service.system.WorkspaceService; -import org.dromara.jpom.system.AgentException; -import org.dromara.jpom.system.AuthorizeException; -import org.springframework.util.Assert; import javax.servlet.http.HttpServletRequest; import java.util.*; @@ -74,25 +72,18 @@ public abstract class BaseNodeService extends BaseGloba this.dataName = dataName; } - public PageResultDto listPageNode(HttpServletRequest request) { - // 验证工作空间权限 - Map paramMap = ServletUtil.getParamMap(request); + @Override + public List listByWorkspace(HttpServletRequest request) { String workspaceId = this.getCheckUserWorkspace(request); - // 验证节点 - String nodeId = paramMap.get(BaseServerController.NODE_ID); - Assert.notNull(nodeId, "没有选择节点ID"); - NodeService nodeService = SpringUtil.getBean(NodeService.class); - NodeModel nodeModel = nodeService.getByKey(nodeId); - Assert.notNull(nodeModel, "不存在对应的节点"); - Assert.state(StrUtil.equals(workspaceId, nodeModel.getWorkspaceId()), "节点的工作空间和操作的工作空间补一致"); - paramMap.remove("workspaceId"); - paramMap.put("nodeId", nodeId); - paramMap.put("workspaceId:in", workspaceId + StrUtil.COMMA + ServerConst.WORKSPACE_GLOBAL); - - return super.listPage(paramMap); + Map paramMap = ServletUtil.getParamMap(request); + String nodeId = paramMap.get("nodeId"); + Entity entity = Entity.create(); + entity.set("workspaceId", CollUtil.newArrayList(workspaceId, ServerConst.WORKSPACE_GLOBAL)); + Opt.ofBlankAble(nodeId).ifPresent(s -> entity.set("nodeId", s)); + List entities = super.queryList(entity); + return super.entityToBeanList(entities); } - /** * 同步所有节点的项目 */ @@ -129,6 +120,81 @@ public abstract class BaseNodeService extends BaseGloba ThreadUtil.execute(() -> this.syncExecuteNode(nodeModel)); } + /** + * 检查孤独数据 + * + * @param jsonArray 数据 + * @param machineId 机器 ID + * @return list + */ + protected List checkLonelyDataArray(JSONArray jsonArray, String machineId) { + if (CollUtil.isEmpty(jsonArray)) { + return null; + } + // 分组 + Map> map = jsonArray.stream().map(o -> { + JSONObject jsonObject = (JSONObject) o; + return jsonObject.to(tClass); + }).collect(Collectors.groupingBy( + t -> StrUtil.emptyToDefault(t.getNodeId(), StrUtil.EMPTY) + StrUtil.COMMA + t.getWorkspaceId(), + Collectors.mapping(t -> t, Collectors.toList()) + )); + // 查询不存在的节点 + Map nodeIdMap = new HashMap<>(); + return map.entrySet() + .stream() + .filter(entry -> { + String key = entry.getKey(); + if (StrUtil.startWith(key, StrUtil.COMMA)) { + // 旧数据没有节点 ID + List list = StrUtil.splitTrim(key, StrUtil.COMMA); + String workspaceId = CollUtil.getLast(list); + NodeModel nodeModel = new NodeModel(); + nodeModel.setMachineId(machineId); + nodeModel.setWorkspaceId(workspaceId); + // 更新推荐节点ID + NodeModel queryByBean = nodeService.queryByBean(nodeModel); + if (queryByBean != null) { + String beanId = queryByBean.getId(); + String s = nodeIdMap.put(key, beanId); + if (StrUtil.isNotEmpty(s) && !StrUtil.equals(s, beanId)) { + // 对比已经存在的数据 + log.error("项目数据工作空间ID[{}]查询出节点ID不一致, 旧数据: {}, 新数据: {}", key, s, beanId); + } + } + return true; + } + List list = StrUtil.splitTrim(key, StrUtil.COMMA); + if (CollUtil.size(list) != 2) { + return true; + } + String workspaceId = list.get(1); + String id = list.get(0); + if (StrUtil.equals(workspaceId, ServerConst.WORKSPACE_GLOBAL)) { + // 判断全局工作空间ID ,判断节点不存在 + return !nodeService.exists(id); + } + NodeModel nodeModel = new NodeModel(); + nodeModel.setId(id); + nodeModel.setWorkspaceId(workspaceId); + return !nodeService.exists(nodeModel); + + }) + .peek(entry -> { + String key = entry.getKey(); + String nodeId = nodeIdMap.get(key); + if (nodeId != null) { + // 更新节点ID + List value = entry.getValue(); + for (T t : value) { + t.setNodeId(nodeId); + } + } + }) + .flatMap(entry -> entry.getValue().stream()) + .collect(Collectors.toList()); + } + /** * 同步执行 同步节点信息 * @@ -162,30 +228,33 @@ public abstract class BaseNodeService extends BaseGloba .map(BaseNodeModel::dataId) .collect(Collectors.toSet()); // 转换数据修改时间 - List projectInfoModels = jsonArray.stream().map(o -> { - // modifyTime,createTime - JSONObject jsonObject = (JSONObject) o; - T t = jsonObject.to(tClass); - Opt.ofBlankAble(jsonObject.getString("createTime")).map(s -> { - try { - return DateUtil.parse(s); - } catch (Exception e) { - log.warn("数据创建时间格式不正确 {} {}", s, jsonObject); - return null; - } - }).ifPresent(s -> t.setCreateTimeMillis(s.getTime())); - // - Opt.ofBlankAble(jsonObject.getString("modifyTime")).map(s -> { - try { - return DateUtil.parse(s); - } catch (Exception e) { - log.warn("数据修改时间格式不正确 {} {}", s, jsonObject); - return null; - } - }).ifPresent(s -> t.setModifyTimeMillis(s.getTime())); - return t; - }).collect(Collectors.toList()); - List models = projectInfoModels.stream() + List projectInfoModels = jsonArray.stream() + .map(o -> { + // modifyTime,createTime + JSONObject jsonObject = (JSONObject) o; + T t = jsonObject.to(tClass); + Opt.ofBlankAble(jsonObject.getString("createTime")) + .map(s -> { + try { + return DateUtil.parse(s); + } catch (Exception e) { + log.warn("数据创建时间格式不正确 {} {}", s, jsonObject); + return null; + } + }).ifPresent(s -> t.setCreateTimeMillis(s.getTime())); + // + Opt.ofBlankAble(jsonObject.getString("modifyTime")) + .map(s -> { + try { + return DateUtil.parse(s); + } catch (Exception e) { + log.warn("数据修改时间格式不正确 {} {}", s, jsonObject); + return null; + } + }) + .ifPresent(s -> t.setModifyTimeMillis(s.getTime())); + return t; + }) .peek(item -> this.fullData(item, nodeModel)) // 只保留自己节点的数据 .filter(t -> StrUtil.equals(t.getNodeId(), nodeModel.getId())) @@ -219,7 +288,7 @@ public abstract class BaseNodeService extends BaseGloba // 设置 临时缓存,便于放行检查 BaseServerController.resetInfo(UserModel.EMPTY); // - models.forEach(BaseNodeService.super::upsert); + projectInfoModels.forEach(BaseNodeService.super::upsert); // 删除项目 int delCount = 0; Set strings = cacheIds.stream() @@ -232,12 +301,14 @@ public abstract class BaseNodeService extends BaseGloba if (CollUtil.isNotEmpty(needDelete)) { delCount = super.delByKey(needDelete, null); } + int size = CollUtil.size(projectInfoModels); String format = StrUtil.format( - "{} 节点拉取到 {} 个{},已经缓存 {} 个{},更新 {} 个{},删除 {} 个缓存", + "{} 物理节点拉取到 {} 个{},当前工作空间逻辑节点已经缓存 {} 个{},更新 {} 个{},删除 {} 个缓存", nodeModelName, CollUtil.size(jsonArray), dataName, CollUtil.size(cacheAll), dataName, - CollUtil.size(models), dataName, + size, dataName, delCount); + this.refreshCacheStat(nodeModel.getId(), size); log.debug(format); return format; } catch (Exception e) { @@ -247,15 +318,25 @@ public abstract class BaseNodeService extends BaseGloba } } + /** + * 刷新缓存统计 + * + * @param nodeId 节点id + * @param dataCount 数据总数 + */ + protected void refreshCacheStat(String nodeId, int dataCount) { + + } + protected String checkException(Exception e, String nodeModelName) { if (e instanceof AgentException) { AgentException agentException = (AgentException) e; log.error("{} 同步失败 {}", nodeModelName, agentException.getMessage()); return "同步失败" + agentException.getMessage(); - } else if (e instanceof AuthorizeException) { - AuthorizeException authorizeException = (AuthorizeException) e; - log.error("{} 授权异常 {}", nodeModelName, authorizeException.getMessage()); - return "授权异常" + authorizeException.getMessage(); + } else if (e instanceof AgentAuthorizeException) { + AgentAuthorizeException agentAuthorizeException = (AgentAuthorizeException) e; + log.error("{} 授权异常 {}", nodeModelName, agentAuthorizeException.getMessage()); + return "授权异常" + agentAuthorizeException.getMessage(); } // else if (e instanceof JSONException) { // log.error("{} 消息解析失败 {}", nodeModelName, e.getMessage()); @@ -383,4 +464,12 @@ public abstract class BaseNodeService extends BaseGloba * @return json */ public abstract JSONArray getLitDataArray(NodeModel nodeModel); + + /** + * 查询孤立的数据 + * + * @param machineNodeModel 资产 + * @return json + */ + public abstract List lonelyDataArray(MachineNodeModel machineNodeModel); } diff --git a/modules/server/src/main/java/org/dromara/jpom/service/h2db/BaseWorkspaceService.java b/modules/server/src/main/java/org/dromara/jpom/service/h2db/BaseWorkspaceService.java index b12e5f423..4df9caab2 100644 --- a/modules/server/src/main/java/org/dromara/jpom/service/h2db/BaseWorkspaceService.java +++ b/modules/server/src/main/java/org/dromara/jpom/service/h2db/BaseWorkspaceService.java @@ -35,6 +35,7 @@ import cn.hutool.extra.spring.SpringUtil; import org.dromara.jpom.common.BaseServerController; import org.dromara.jpom.common.Const; import org.dromara.jpom.common.ServerConst; +import org.dromara.jpom.exception.PermissionException; import org.dromara.jpom.model.BaseWorkspaceModel; import org.dromara.jpom.model.PageResultDto; import org.dromara.jpom.model.user.UserModel; @@ -262,7 +263,9 @@ public abstract class BaseWorkspaceService extends // 查询绑定的权限 UserBindWorkspaceService userBindWorkspaceService = SpringUtil.getBean(UserBindWorkspaceService.class); boolean exists = userBindWorkspaceService.exists(userModel, workspaceId); - Assert.state(exists, "没有对应的工作空间权限"); + if (!exists) { + throw new PermissionException("没有对应的工作空间权限"); + } } diff --git a/modules/server/src/main/java/org/dromara/jpom/service/node/ProjectInfoCacheService.java b/modules/server/src/main/java/org/dromara/jpom/service/node/ProjectInfoCacheService.java index f970546f3..17b85087e 100644 --- a/modules/server/src/main/java/org/dromara/jpom/service/node/ProjectInfoCacheService.java +++ b/modules/server/src/main/java/org/dromara/jpom/service/node/ProjectInfoCacheService.java @@ -27,6 +27,7 @@ import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONObject; import org.dromara.jpom.common.forward.NodeForward; import org.dromara.jpom.common.forward.NodeUrl; +import org.dromara.jpom.func.assets.model.MachineNodeModel; import org.dromara.jpom.model.data.NodeModel; import org.dromara.jpom.model.node.ProjectInfoCacheModel; import org.dromara.jpom.service.ITriggerToken; @@ -34,6 +35,8 @@ import org.dromara.jpom.service.h2db.BaseNodeService; import org.dromara.jpom.service.system.WorkspaceService; import org.springframework.stereotype.Service; +import java.util.List; + /** * @author bwcx_jzy * @since 2021/12/5 @@ -108,8 +111,24 @@ public class ProjectInfoCacheService extends BaseNodeService lonelyDataArray(MachineNodeModel machineNodeModel) { + JSONObject jsonObject = new JSONObject(); + jsonObject.put("notStatus", true); + JsonMessage tJsonMessage = NodeForward.request(machineNodeModel, NodeUrl.Manage_GetProjectInfo, jsonObject); + return this.checkLonelyDataArray(tJsonMessage.getData(), machineNodeModel.getId()); + } + @Override public String typeName() { return getTableName(); } + + @Override + protected void refreshCacheStat(String nodeId, int dataCount) { + NodeModel nodeModel = new NodeModel(); + nodeModel.setId(nodeId); + nodeModel.setJpomProjectCount(dataCount); + nodeService.updateById(nodeModel); + } } diff --git a/modules/server/src/main/java/org/dromara/jpom/service/node/script/NodeScriptExecuteLogServer.java b/modules/server/src/main/java/org/dromara/jpom/service/node/script/NodeScriptExecuteLogServer.java index ae138efd5..cc3cba049 100644 --- a/modules/server/src/main/java/org/dromara/jpom/service/node/script/NodeScriptExecuteLogServer.java +++ b/modules/server/src/main/java/org/dromara/jpom/service/node/script/NodeScriptExecuteLogServer.java @@ -33,6 +33,8 @@ import org.dromara.jpom.common.BaseServerController; import org.dromara.jpom.common.ServerConst; import org.dromara.jpom.common.forward.NodeForward; import org.dromara.jpom.common.forward.NodeUrl; +import org.dromara.jpom.exception.AgentException; +import org.dromara.jpom.func.assets.model.MachineNodeModel; import org.dromara.jpom.model.BaseDbModel; import org.dromara.jpom.model.data.NodeModel; import org.dromara.jpom.model.data.WorkspaceModel; @@ -41,7 +43,6 @@ import org.dromara.jpom.model.user.UserModel; import org.dromara.jpom.service.h2db.BaseNodeService; import org.dromara.jpom.service.node.NodeService; import org.dromara.jpom.service.system.WorkspaceService; -import org.dromara.jpom.system.AgentException; import org.springframework.stereotype.Service; import java.util.Collection; @@ -91,7 +92,11 @@ public class NodeScriptExecuteLogServer extends BaseNodeService lonelyDataArray(MachineNodeModel machineNodeModel) { + throw new IllegalStateException("不支持的模式,script log"); } @Override @@ -118,7 +123,8 @@ public class NodeScriptExecuteLogServer extends BaseNodeService models = jsonArray.toJavaList(this.tClass).stream() + List models = jsonArray.toJavaList(this.tClass) + .stream() .filter(item -> { if (StrUtil.equals(item.getWorkspaceId(), ServerConst.WORKSPACE_GLOBAL)) { return true; @@ -139,7 +145,7 @@ public class NodeScriptExecuteLogServer extends BaseNodeService impl return NodeForward.requestData(nodeModel, NodeUrl.Script_List, null, JSONArray.class); } + @Override + public List lonelyDataArray(MachineNodeModel machineNodeModel) { + JSONArray jsonArray = NodeForward.requestData(machineNodeModel, NodeUrl.Script_List, null, JSONArray.class); + return this.checkLonelyDataArray(jsonArray, machineNodeModel.getId()); + } + @Override public String typeName() { return getTableName(); } + + @Override + protected void refreshCacheStat(String nodeId, int dataCount) { + NodeModel nodeModel = new NodeModel(); + nodeModel.setId(nodeId); + nodeModel.setJpomScriptCount(dataCount); + nodeService.updateById(nodeModel); + } } diff --git a/modules/server/src/main/java/org/dromara/jpom/service/node/ssh/CommandService.java b/modules/server/src/main/java/org/dromara/jpom/service/node/ssh/SshCommandService.java similarity index 82% rename from modules/server/src/main/java/org/dromara/jpom/service/node/ssh/CommandService.java rename to modules/server/src/main/java/org/dromara/jpom/service/node/ssh/SshCommandService.java index 6ce0f8473..51e82c328 100644 --- a/modules/server/src/main/java/org/dromara/jpom/service/node/ssh/CommandService.java +++ b/modules/server/src/main/java/org/dromara/jpom/service/node/ssh/SshCommandService.java @@ -22,7 +22,6 @@ */ package org.dromara.jpom.service.node.ssh; -import cn.hutool.core.exceptions.ExceptionUtil; import cn.hutool.core.thread.ThreadUtil; import cn.hutool.core.util.CharsetUtil; import cn.hutool.core.util.IdUtil; @@ -67,7 +66,7 @@ import java.util.Map; */ @Service @Slf4j -public class CommandService extends BaseWorkspaceService implements ICron, ITriggerToken { +public class SshCommandService extends BaseWorkspaceService implements ICron, ITriggerToken { private final SshService sshService; private final CommandExecLogService commandExecLogService; @@ -75,9 +74,9 @@ public class CommandService extends BaseWorkspaceService implement private static final byte[] LINE_BYTES = SystemUtil.getOsInfo().getLineSeparator().getBytes(CharsetUtil.CHARSET_UTF_8); - public CommandService(SshService sshService, - CommandExecLogService commandExecLogService, - WorkspaceEnvVarService workspaceEnvVarService) { + public SshCommandService(SshService sshService, + CommandExecLogService commandExecLogService, + WorkspaceEnvVarService workspaceEnvVarService) { this.sshService = sshService; this.commandExecLogService = commandExecLogService; this.workspaceEnvVarService = workspaceEnvVarService; @@ -125,7 +124,7 @@ public class CommandService extends BaseWorkspaceService implement return false; } log.debug("start ssh command cron {} {} {}", id, buildInfoModel.getName(), autoExecCron); - CronUtils.upsert(taskId, autoExecCron, new CommandService.CronTask(id)); + CronUtils.upsert(taskId, autoExecCron, new SshCommandService.CronTask(id)); return true; } @@ -155,8 +154,8 @@ public class CommandService extends BaseWorkspaceService implement public void execute() { try { BaseServerController.resetInfo(UserModel.EMPTY); - CommandModel commandModel = CommandService.this.getByKey(this.id); - CommandService.this.executeBatch(commandModel, commandModel.getDefParams(), commandModel.getSshIds(), 1); + CommandModel commandModel = SshCommandService.this.getByKey(this.id); + SshCommandService.this.executeBatch(commandModel, commandModel.getDefParams(), commandModel.getSshIds(), 1); } catch (Exception e) { log.error("触发自动执行命令模版异常", e); } finally { @@ -256,39 +255,39 @@ public class CommandService extends BaseWorkspaceService implement */ private void execute(CommandModel commandModel, CommandExecLogModel commandExecLogModel, SshModel sshModel, String commandParamsLine) { File file = commandExecLogModel.logFile(); - LogRecorder logRecorder = LogRecorder.builder().file(file).charset(CharsetUtil.CHARSET_UTF_8).build(); - if (sshModel == null) { - logRecorder.systemError("ssh 不存在"); - return; - } - EnvironmentMapBuilder environmentMapBuilder = workspaceEnvVarService.getEnv(commandModel.getWorkspaceId()); - environmentMapBuilder.put("JPOM_SSH_ID", sshModel.getId()); - environmentMapBuilder.put("JPOM_COMMAND_ID", commandModel.getId()); - environmentMapBuilder.eachStr(logRecorder::system); - Map environment = environmentMapBuilder.environment(); - String commands = StringUtil.formatStrByMap(commandModel.getCommand(), environment); + try (LogRecorder logRecorder = LogRecorder.builder().file(file).charset(CharsetUtil.CHARSET_UTF_8).build()) { + if (sshModel == null) { + logRecorder.systemError("ssh 不存在"); + return; + } + EnvironmentMapBuilder environmentMapBuilder = workspaceEnvVarService.getEnv(commandModel.getWorkspaceId()); + environmentMapBuilder.put("JPOM_SSH_ID", sshModel.getId()); + environmentMapBuilder.put("JPOM_COMMAND_ID", commandModel.getId()); + environmentMapBuilder.eachStr(logRecorder::system); + Map environment = environmentMapBuilder.environment(); + String commands = StringUtil.formatStrByMap(commandModel.getCommand(), environment); - MachineSshModel machineSshModel = sshService.getMachineSshModel(sshModel); - // - Session session = null; - try { - Charset charset = machineSshModel.charset(); - int timeout = machineSshModel.timeout(); + MachineSshModel machineSshModel = sshService.getMachineSshModel(sshModel); // - session = sshService.getSessionByModel(machineSshModel); - int exitCode = JschUtils.execCallbackLine(session, charset, timeout, commands, commandParamsLine, logRecorder::info); - logRecorder.system("执行退出码:{}", exitCode); - // 更新状态 - this.updateStatus(commandExecLogModel.getId(), CommandExecLogModel.Status.DONE, exitCode); - } catch (Exception e) { - log.error("执行命令错误", e); - // 更新状态 - this.updateStatus(commandExecLogModel.getId(), CommandExecLogModel.Status.ERROR); - // 记录错误日志 - String stacktraceToString = ExceptionUtil.stacktraceToString(e); - logRecorder.systemError(stacktraceToString); - } finally { - JschUtil.close(session); + Session session = null; + try { + Charset charset = machineSshModel.charset(); + int timeout = machineSshModel.timeout(); + // + session = sshService.getSessionByModel(machineSshModel); + int exitCode = JschUtils.execCallbackLine(session, charset, timeout, commands, commandParamsLine, logRecorder::info); + logRecorder.system("执行退出码:{}", exitCode); + // 更新状态 + this.updateStatus(commandExecLogModel.getId(), CommandExecLogModel.Status.DONE, exitCode); + } catch (Exception e) { + log.error("执行命令错误", e); + // 更新状态 + this.updateStatus(commandExecLogModel.getId(), CommandExecLogModel.Status.ERROR); + // 记录错误日志 + logRecorder.error("执行命令错误", e); + } finally { + JschUtil.close(session); + } } } diff --git a/modules/server/src/main/java/org/dromara/jpom/service/system/WhitelistDirectoryService.java b/modules/server/src/main/java/org/dromara/jpom/service/system/WhitelistDirectoryService.java index 53a75613d..d3d933572 100644 --- a/modules/server/src/main/java/org/dromara/jpom/service/system/WhitelistDirectoryService.java +++ b/modules/server/src/main/java/org/dromara/jpom/service/system/WhitelistDirectoryService.java @@ -32,7 +32,7 @@ import org.springframework.stereotype.Service; import java.util.List; /** - * 白名单 + * 授权 * * @author bwcx_jzy * @since 2019/4/16 @@ -49,7 +49,7 @@ public class WhitelistDirectoryService { } /** - * 获取项目路径白名单 + * 获取项目路径授权 * * @param model 实体 * @return project diff --git a/modules/server/src/main/java/org/dromara/jpom/service/user/CleanTriggerTokenService.java b/modules/server/src/main/java/org/dromara/jpom/service/user/CleanTriggerTokenService.java deleted file mode 100644 index 1266a09e7..000000000 --- a/modules/server/src/main/java/org/dromara/jpom/service/user/CleanTriggerTokenService.java +++ /dev/null @@ -1,111 +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.user; - -import cn.hutool.core.date.BetweenFormatter; -import cn.hutool.core.date.DateUtil; -import cn.hutool.core.date.SystemClock; -import cn.hutool.db.Entity; -import cn.keepbx.jpom.event.ISystemTask; -import lombok.extern.slf4j.Slf4j; -import org.dromara.jpom.model.user.TriggerTokenLogBean; -import org.dromara.jpom.service.h2db.BaseDbService; -import org.springframework.stereotype.Service; - -import java.util.ArrayList; -import java.util.List; -import java.util.stream.Collectors; - -/** - * 清理触发器 token - * - * @author Hotstrip - * @since 2023-04-13 - */ -@Service -@Slf4j -public class CleanTriggerTokenService extends BaseDbService implements ISystemTask { - - @Override - public void executeTask() { - log.info("clean trigger token start..."); - long start = SystemClock.now(); - - // 调用方法处理逻辑 - cleanTriggerToken(); - - log.info("clean trigger token end... cost time: {}", DateUtil.formatBetween(SystemClock.now() - start, BetweenFormatter.Level.MILLISECOND)); - } - - private void cleanTriggerToken() { - // 查询数据库中的数据 - List triggerTokenList = list(); - if (triggerTokenList == null || triggerTokenList.isEmpty()) { - log.debug("trigger token list is empty, no need to clean"); - return; - } - - List dataIdList = queryDataIdList(); - - // 统计删除条数 - int delCount = 0; - - // 遍历数据,判断是否应该删除 - for (TriggerTokenLogBean triggerToken : triggerTokenList) { - if (!dataIdList.contains(triggerToken.getDataId())) { - // 删除数据 - delByKey(triggerToken.getId()); - log.info("delete trigger token: {}", triggerToken); - delCount++; - } - } - - log.info("clean trigger token count: {}", delCount); - } - - private List queryDataIdList() { - List dataIdList = new ArrayList<>(); - // 根据触发器类型 type 分组查询触发器数据关联的表 - String sql = String.format("select TYPE from %s group by TYPE", this.getTableName()); - List triggerTokenTypeList = queryList(sql); - if (triggerTokenTypeList == null || triggerTokenTypeList.isEmpty()) { - log.debug("trigger token type list is empty, no need to clean"); - return dataIdList; - } - - // 遍历数据,分别查询对应的数据库表 - triggerTokenTypeList.forEach(item -> { - // 构造 sql 查询对应表数据的 ID - String selectSql = String.format("select ID from %s", item.getType()); - List entityList = query(selectSql); - if (entityList != null) { - // 遍历数据,获取 ID - dataIdList.addAll(entityList.stream() - .map(entity -> entity.getStr("ID")) - .collect(Collectors.toList())); - } - }); - - return dataIdList; - } -} diff --git a/modules/server/src/main/java/org/dromara/jpom/service/user/TriggerTokenLogServer.java b/modules/server/src/main/java/org/dromara/jpom/service/user/TriggerTokenLogServer.java index 4276fbd33..ea0965274 100644 --- a/modules/server/src/main/java/org/dromara/jpom/service/user/TriggerTokenLogServer.java +++ b/modules/server/src/main/java/org/dromara/jpom/service/user/TriggerTokenLogServer.java @@ -22,17 +22,28 @@ */ package org.dromara.jpom.service.user; -import cn.hutool.core.collection.CollUtil; +import cn.hutool.core.date.BetweenFormatter; +import cn.hutool.core.date.DateUtil; +import cn.hutool.core.date.SystemClock; import cn.hutool.core.util.IdUtil; import cn.hutool.core.util.StrUtil; +import cn.hutool.db.Db; +import cn.hutool.db.handler.RsHandler; +import cn.keepbx.jpom.event.ISystemTask; import lombok.extern.slf4j.Slf4j; +import org.dromara.jpom.db.StorageServiceFactory; import org.dromara.jpom.model.user.TriggerTokenLogBean; import org.dromara.jpom.model.user.UserModel; +import org.dromara.jpom.service.ITriggerToken; import org.dromara.jpom.service.h2db.BaseDbService; import org.springframework.stereotype.Service; +import org.springframework.util.Assert; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.ArrayList; import java.util.List; -import java.util.Optional; /** * @author bwcx_jzy @@ -40,12 +51,26 @@ import java.util.Optional; */ @Service @Slf4j -public class TriggerTokenLogServer extends BaseDbService { +public class TriggerTokenLogServer extends BaseDbService implements ISystemTask { private final UserService userService; + private final List triggerTokens; - public TriggerTokenLogServer(UserService userService) { + public TriggerTokenLogServer(UserService userService, + List triggerTokens) { this.userService = userService; + this.triggerTokens = triggerTokens; + } + + /** + * 通过用户ID 删除数据 + * + * @param userId 用户d + */ + public void delByUserId(String userId) { + TriggerTokenLogBean tokenLogBean = new TriggerTokenLogBean(); + tokenLogBean.setUserId(userId); + this.delByBean(tokenLogBean); } /** @@ -60,18 +85,13 @@ public class TriggerTokenLogServer extends BaseDbService { if (tokenLogBean != null) { UserModel userModel = userService.getByKey(tokenLogBean.getUserId()); if (userModel != null && StrUtil.equals(type, tokenLogBean.getType())) { + boolean demoUser = userModel.isDemoUser(); + Assert.state(!demoUser, "当前用户触发器不可用"); return userModel; } } - // 兼容旧版本数据 - TriggerTokenLogBean where = new TriggerTokenLogBean(); - where.setTriggerToken(token); - where.setType(type); - List triggerTokenLogBeans = this.listByBean(where); - return Optional.ofNullable(triggerTokenLogBeans) - .map(CollUtil::getFirst) - .map(triggerTokenLogBean -> userService.getByKey(triggerTokenLogBean.getUserId())) - .orElse(null); + // + return null; } /** @@ -85,20 +105,7 @@ public class TriggerTokenLogServer extends BaseDbService { */ public String restToken(String oldToken, String type, String dataId, String userId) { if (StrUtil.isNotEmpty(oldToken)) { - TriggerTokenLogBean tokenLogBean = this.getByKey(oldToken); - if (tokenLogBean != null) { - this.delByKey(oldToken); - } else { - // 可能存在之前版本数据 - TriggerTokenLogBean where = new TriggerTokenLogBean(); - where.setTriggerToken(oldToken); - where.setType(type); - List triggerTokenLogBeans = this.listByBean(where); - Optional.ofNullable(triggerTokenLogBeans) - .ifPresent(triggerTokenLogBeans1 -> - triggerTokenLogBeans1.forEach(triggerTokenLogBean -> this.delByKey(triggerTokenLogBean.getId())) - ); - } + this.delByKey(oldToken); } // 创建 token return this.createToken(type, dataId, userId); @@ -123,4 +130,61 @@ public class TriggerTokenLogServer extends BaseDbService { this.insert(trigger); return uuid; } + + @Override + public void executeTask() { + if (triggerTokens == null) { + return; + } + log.debug("clean trigger token start..."); + long start = SystemClock.now(); + // 调用方法处理逻辑 + cleanTriggerToken(); + log.debug("clean trigger token end... cost time: {}", DateUtil.formatBetween(SystemClock.now() - start, BetweenFormatter.Level.MILLISECOND)); + } + + /** + * @author Hotstrip + * @since 2023-04-13 + */ + private void cleanTriggerToken() { + // 统计删除条数 + int delCount = 0; + int fetchSize = StorageServiceFactory.get().getFetchSize(); + for (ITriggerToken triggerToken : triggerTokens) { + TriggerTokenLogBean tokenLogBean = new TriggerTokenLogBean(); + tokenLogBean.setType(triggerToken.typeName()); + try { + delCount += Db.use(this.getDataSource()) + .query((conn -> { + PreparedStatement ps = conn.prepareStatement("select dataId,id from " + this.getTableName() + " where type=?", + ResultSet.TYPE_FORWARD_ONLY, + ResultSet.CONCUR_READ_ONLY); + ps.setString(1, triggerToken.typeName()); + ps.setFetchSize(fetchSize); + ps.setFetchDirection(ResultSet.FETCH_FORWARD); + return ps; + }), (RsHandler) rs -> { + List ids = new ArrayList<>(); + while (rs.next()) { + // + String dataId = rs.getString("dataId"); + if (triggerToken.exists(dataId)) { + continue; + } + String id = rs.getString("id"); + ids.add(id); + } + // 删除 token + this.delByKey(ids); + return ids.size(); + }); + } catch (SQLException e) { + log.error("执行清理 token[{}] 异常", triggerToken.typeName(), e); + } + } + if (delCount > 0) { + log.info("clean trigger token count: {}", delCount); + } + } } diff --git a/modules/server/src/main/java/org/dromara/jpom/service/user/UserBindWorkspaceService.java b/modules/server/src/main/java/org/dromara/jpom/service/user/UserBindWorkspaceService.java index 472234ef1..d12490cd8 100644 --- a/modules/server/src/main/java/org/dromara/jpom/service/user/UserBindWorkspaceService.java +++ b/modules/server/src/main/java/org/dromara/jpom/service/user/UserBindWorkspaceService.java @@ -56,10 +56,6 @@ public class UserBindWorkspaceService extends BaseDbService list = new HashSet<>(workspace).stream() .filter(s -> { // 过滤 - s = StrUtil.removeSuffix(s, SYSTEM_USER); s = StrUtil.removeSuffix(s, SSH_COMMAND_NOT_LIMITED); MethodFeature[] values = MethodFeature.values(); for (MethodFeature value : values) { diff --git a/modules/server/src/main/java/org/dromara/jpom/socket/BaseHandler.java b/modules/server/src/main/java/org/dromara/jpom/socket/BaseHandler.java index 5e960d366..854e8e9b2 100644 --- a/modules/server/src/main/java/org/dromara/jpom/socket/BaseHandler.java +++ b/modules/server/src/main/java/org/dromara/jpom/socket/BaseHandler.java @@ -96,6 +96,7 @@ public abstract class BaseHandler extends TextWebSocketHandler { @Override public void afterConnectionClosed(WebSocketSession session, CloseStatus status) { destroy(session); + log.debug("会话[{}]关闭原因:{}", session.getId(), status); } /** diff --git a/modules/server/src/main/java/org/dromara/jpom/socket/handler/DockerLogHandler.java b/modules/server/src/main/java/org/dromara/jpom/socket/handler/DockerLogHandler.java index 2e102aca9..2e75cac17 100644 --- a/modules/server/src/main/java/org/dromara/jpom/socket/handler/DockerLogHandler.java +++ b/modules/server/src/main/java/org/dromara/jpom/socket/handler/DockerLogHandler.java @@ -24,6 +24,7 @@ package org.dromara.jpom.socket.handler; import cn.hutool.core.bean.BeanUtil; import cn.hutool.core.io.FileUtil; +import cn.hutool.core.io.IoUtil; import cn.hutool.core.map.MapUtil; import cn.hutool.core.thread.ThreadUtil; import cn.hutool.core.util.CharsetUtil; @@ -119,6 +120,7 @@ public class DockerLogHandler extends BaseProxyHandler { } }; attributes.put("uuid", uuid); + attributes.put("logRecorder", logRecorder); map.put("uuid", attributes.get("uuid")); map.put("charset", CharsetUtil.CHARSET_UTF_8); map.put("consumer", consumer); @@ -159,6 +161,8 @@ public class DockerLogHandler extends BaseProxyHandler { } catch (Exception e) { log.error("关闭资源失败", e); } + LogRecorder logRecorder = (LogRecorder) attributes.get("logRecorder"); + IoUtil.close(logRecorder); // 删除日志缓存 UserModel userModel = (UserModel) attributes.get("userInfo"); Optional.ofNullable(userModel).ifPresent(userModel1 -> { diff --git a/modules/server/src/main/java/org/dromara/jpom/system/ServerConfig.java b/modules/server/src/main/java/org/dromara/jpom/system/ServerConfig.java index 60dfb3646..ace21fdec 100644 --- a/modules/server/src/main/java/org/dromara/jpom/system/ServerConfig.java +++ b/modules/server/src/main/java/org/dromara/jpom/system/ServerConfig.java @@ -24,15 +24,17 @@ package org.dromara.jpom.system; import cn.hutool.core.io.FileUtil; import cn.hutool.core.lang.Validator; +import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import lombok.Data; -import lombok.EqualsAndHashCode; import org.dromara.jpom.JpomApplication; import org.dromara.jpom.common.BaseServerController; import org.dromara.jpom.configuration.*; import org.dromara.jpom.model.AgentFileModel; import org.dromara.jpom.model.user.UserModel; +import org.dromara.jpom.util.BaseFileTailWatcher; import org.springframework.beans.factory.InitializingBean; +import org.springframework.beans.factory.annotation.Value; import org.springframework.boot.context.properties.ConfigurationProperties; import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Configuration; @@ -44,12 +46,17 @@ import java.util.Optional; * @author bwcx_jzy * @since 2022/12/17 */ -@EqualsAndHashCode(callSuper = true) @Configuration @ConfigurationProperties("jpom") -@EnableConfigurationProperties({ClusterConfig.class, SystemConfig.class, NodeConfig.class, UserConfig.class, FileStorageConfig.class, WebConfig.class}) +@EnableConfigurationProperties({ + ClusterConfig.class, + SystemConfig.class, + NodeConfig.class, + UserConfig.class, + FileStorageConfig.class, + WebConfig.class}) @Data -public class ServerConfig extends BaseExtConfig implements InitializingBean { +public class ServerConfig implements InitializingBean { private final JpomApplication configBean; @@ -57,6 +64,15 @@ public class ServerConfig extends BaseExtConfig implements InitializingBean { this.configBean = configBean; } + /** + * 数据目录 + */ + private String path; + + /** + * 初始读取日志文件行号 + */ + private Integer initReadLine = 10; /** * 集群 配置信息 */ @@ -80,7 +96,7 @@ public class ServerConfig extends BaseExtConfig implements InitializingBean { /** * 文件中心配置 */ - private FileStorageConfig fileStorage; + private FileStorageConfig fileStorage = new FileStorageConfig(); public SystemConfig getSystem() { return Optional.ofNullable(this.system).orElseGet(() -> { @@ -181,6 +197,10 @@ public class ServerConfig extends BaseExtConfig implements InitializingBean { if (!Validator.isGeneral(clusterId1, 1, 20)) { throw new JpomRuntimeException("请配置正确的集群Id,【jpom.clusterId】"); } + + int initReadLine = ObjectUtil.defaultIfNull(this.initReadLine, 10); + BaseFileTailWatcher.setInitReadLine(initReadLine); + ExtConfigBean.setPath(path); } @@ -190,9 +210,6 @@ public class ServerConfig extends BaseExtConfig implements InitializingBean { * @return path */ public File fileStorageSavePath() { - if (fileStorage == null) { - fileStorage = new FileStorageConfig(); - } String savePah = fileStorage.getSavePah(); if (StrUtil.isEmpty(savePah)) { String dataPath = configBean.getDataPath(); diff --git a/modules/server/src/main/resources/application.yml b/modules/server/src/main/resources/application.yml index d731a784c..1e52d6ac1 100644 --- a/modules/server/src/main/resources/application.yml +++ b/modules/server/src/main/resources/application.yml @@ -107,6 +107,11 @@ jpom: fileStorage: # 文件中心存储路径 save-pah: + scan-static-dir-cron: 0 0/1 * * * + # 是否开启静态文件目录监听 + watch-monitor-static-dir: true + # 监听静态文件目录层级 + watch-monitor-max-depth: 5 assets: ssh: monitor-cron: 0 0/1 * * * ? diff --git a/modules/server/src/main/resources/config_default/application.yml b/modules/server/src/main/resources/config_default/application.yml index 7c84b4bd3..6ef67e1a0 100644 --- a/modules/server/src/main/resources/config_default/application.yml +++ b/modules/server/src/main/resources/config_default/application.yml @@ -96,6 +96,11 @@ jpom: file-storage: # 文件中心存储路径 save-pah: + scan-static-dir-cron: 0 0/1 * * * + # 是否开启静态文件目录监听 + watch-monitor-static-dir: true + # 监听静态文件目录层级 + watch-monitor-max-depth: 5 assets: # ssh 资产 ssh: diff --git a/modules/server/src/main/resources/config_default/exec/template.bat b/modules/server/src/main/resources/config_default/exec/template.bat index 1e13431e8..dd21b0ea8 100644 --- a/modules/server/src/main/resources/config_default/exec/template.bat +++ b/modules/server/src/main/resources/config_default/exec/template.bat @@ -28,3 +28,5 @@ @REM Set environment variables to prevent some servers from failing to taskkill set PATH = %PATH%;C:\Windows\system32;C:\Windows;C:\Windows\system32\Wbem + +@REM Do not delete the following content (leave at least one blank line)! ! ! ! ! diff --git a/modules/server/src/main/resources/config_default/exec/template.sh b/modules/server/src/main/resources/config_default/exec/template.sh index 09521af8e..7adbe9c97 100644 --- a/modules/server/src/main/resources/config_default/exec/template.sh +++ b/modules/server/src/main/resources/config_default/exec/template.sh @@ -23,7 +23,7 @@ # # Mistakenly deleted !!!!!!!!!!! -# Init script templates for local build, local publish, script template, ssh publish, ssh command template and other related functions +# Init script templates for local build, local publish, script template and other related functions user="$(id -un 2>/dev/null || true)" @@ -44,3 +44,5 @@ for element in "${userProfiles[@]}"; do source "$element" fi done + +# Do not delete the following content (leave at least one blank line)! ! ! ! ! diff --git a/modules/server/src/main/resources/config_default/ssh/template.sh b/modules/server/src/main/resources/config_default/ssh/template.sh new file mode 100644 index 000000000..fa45a2067 --- /dev/null +++ b/modules/server/src/main/resources/config_default/ssh/template.sh @@ -0,0 +1,48 @@ +#!/bin/bash +# +# 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. +# + +# Mistakenly deleted !!!!!!!!!!! +# Init script templates for local build, local publish, script template, ssh publish, ssh command template and other related functions + +user="$(id -un 2>/dev/null || true)" + +if [ "$user" == 'root' ]; then + rootProfiles=("/etc/profile" "/etc/bashrc") + for element in "${rootProfiles[@]}"; do + if [ -f "$element" ]; then + # shellcheck disable=SC1090 + source "$element" + fi + done +fi + +userProfiles=("$HOME/.bash_profile" "$HOME/.bashrc" "$HOME/.bash_login") +for element in "${userProfiles[@]}"; do + if [ -f "$element" ]; then + # shellcheck disable=SC1090 + source "$element" + fi +done + +# Do not delete the following content (leave at least one blank line)! ! ! ! ! diff --git a/modules/server/src/main/resources/menus/index.json b/modules/server/src/main/resources/menus/index.json index 9a5d41cf3..0649c0b9b 100644 --- a/modules/server/src/main/resources/menus/index.json +++ b/modules/server/src/main/resources/menus/index.json @@ -1,4 +1,9 @@ [ + { + "title": "概括总览", + "icon_v3": "laptop", + "id": "overview" + }, { "title": "节点&项目", "icon_v3": "apartment", @@ -22,7 +27,7 @@ }, { "id": "outgivingWhitelistDirectory", - "title": "分发白名单", + "title": "授权配置", "role": "system" }, { @@ -135,6 +140,10 @@ "id": "fileStorage", "title": "文件列表" }, + { + "id": "staticFileStorage", + "title": "静态文件" + }, { "id": "fileReleaseTask", "title": "发布任务" diff --git a/modules/server/src/main/resources/menus/node-index.json b/modules/server/src/main/resources/menus/node-index.json deleted file mode 100644 index 774d0bf52..000000000 --- a/modules/server/src/main/resources/menus/node-index.json +++ /dev/null @@ -1,53 +0,0 @@ -[ - { - "id": "welcome", - "title": "首页", - "icon_v3": "dashboard" - }, - { - "id": "manage", - "title": "项目&脚本", - "icon_v3": "project", - "childs": [ - { - "id": "manageList", - "title": "项目列表" - }, - { - "id": "script", - "title": "脚本模板" - }, - { - "id": "script-log", - "title": "脚本执行记录" - } - ] - }, - { - "id": "systemConfig", - "title": "插件端配置", - "icon_v3": "setting", - "childs": [ - { - "id": "whitelistDirectory", - "title": "白名单配置", - "role": "system" - }, - { - "id": "cacheManage", - "title": "缓存管理", - "role": "system" - }, - { - "id": "logManage", - "title": "系统日志", - "role": "system" - }, - { - "id": "sysConfig", - "title": "系统配置", - "role": "system" - } - ] - } -] diff --git a/modules/server/src/main/resources/sql-view/alter.all.v1.4.csv b/modules/server/src/main/resources/sql-view/alter.all.v1.4.csv index 6b73f91da..3e8cec85e 100644 --- a/modules/server/src/main/resources/sql-view/alter.all.v1.4.csv +++ b/modules/server/src/main/resources/sql-view/alter.all.v1.4.csv @@ -3,3 +3,10 @@ ADD,COMMAND_EXEC_LOG,exitCode,Integer,,,退出码 ADD,SERVER_SCRIPT_EXECUTE_LOG,exitCode,Integer,,,退出码 ADD,SERVER_SCRIPT_EXECUTE_LOG,status,TINYINT,,,执行状态 ADD,WORKSPACE_ENV_VAR,triggerToken,String,100,,触发器token +ADD,OUTGIVINGLOG,mode,String,50,,分发方式 +ADD,OUTGIVINGLOG,modeData,String,500,,分发方式数据 +ADD,OUT_GIVING,mode,String,50,,分发方式 +ADD,OUT_GIVING,modeData,String,500,,分发方式数据 +ADD,FILE_RELEASE_TASK_LOG,fileType,TINYINT,,,文件类型 +ADD,NODE_INFO,jpomProjectCount,Integer,,,jpom项目数, +ADD,NODE_INFO,jpomScriptCount,Integer,,,jpom脚本数, diff --git a/modules/server/src/main/resources/sql-view/index.all.v1.0.csv b/modules/server/src/main/resources/sql-view/index.all.v1.0.csv index 1911989ba..7f74c35ce 100644 --- a/modules/server/src/main/resources/sql-view/index.all.v1.0.csv +++ b/modules/server/src/main/resources/sql-view/index.all.v1.0.csv @@ -1,2 +1,7 @@ indexType,tableName,name,field ADD-UNIQUE,USER_INFO,USER_INF_SALT_INDEX1,salt +ADD,STATIC_FILE_STORAGE,DIR_TASK_ID,staticDir+scanTaskId +ADD,STATIC_FILE_STORAGE,DIR_ABS_PATH,staticDir+absolutePath +ADD,STATIC_FILE_STORAGE,DIR_ABS_PATH,staticDir+parentAbsolutePath +ADD,TRIGGER_TOKEN_LOG,TRIGGER_TOKEN_TYPE,type +ADD,TRIGGER_TOKEN_LOG,TRIGGER_TOKEN_USER_ID,userId diff --git a/modules/server/src/main/resources/sql-view/table.all.v1.2.csv b/modules/server/src/main/resources/sql-view/table.all.v1.2.csv index 06d72bf35..b2a1f9e08 100644 --- a/modules/server/src/main/resources/sql-view/table.all.v1.2.csv +++ b/modules/server/src/main/resources/sql-view/table.all.v1.2.csv @@ -10,3 +10,21 @@ CLUSTER_INFO,linkGroup,String,500,,false,false,集群地址, CLUSTER_INFO,lastHeartbeat,Long,,,false,false,最后心跳, CLUSTER_INFO,localHostName,String,255,,false,false,主机名, CLUSTER_INFO,jpomVersion,String,255,,false,false,jpom版本, + +STATIC_FILE_STORAGE,id,String,50,,true,true,id,静态文件管理 +STATIC_FILE_STORAGE,createTimeMillis,Long,,,false,false,数据创建时间, +STATIC_FILE_STORAGE,modifyTimeMillis,Long,,,false,false,数据修改时间, +STATIC_FILE_STORAGE,modifyUser,String,50,,false,false,修改人, +STATIC_FILE_STORAGE,name,String,50,,true,false,集群名称, +STATIC_FILE_STORAGE,description,String,255,,false,false,描述, +STATIC_FILE_STORAGE,extName,String,50,,false,false,扩展名, +STATIC_FILE_STORAGE,absolutePath,String,500,,false,false,文件路径, +STATIC_FILE_STORAGE,parentAbsolutePath,String,500,,false,false,父级文件路径, +STATIC_FILE_STORAGE,staticDir,String,50,,false,false,配置的静态路径, +STATIC_FILE_STORAGE,status,TINYINT,,,false,false,"状态 0 不存在 1 存在", +STATIC_FILE_STORAGE,type,TINYINT,,,false,false,"类型 0 文件夹 1 文件", +STATIC_FILE_STORAGE,scanTaskId,Long,,,false,false,扫描任务id, +STATIC_FILE_STORAGE,lastModified,Long,,,false,false,最后修改时间, +STATIC_FILE_STORAGE,size,Long,,,false,false,文件大小, +STATIC_FILE_STORAGE,level,Integer,,,false,false,"层级", +STATIC_FILE_STORAGE,triggerToken,String,100,,false,false,触发器token, diff --git a/modules/server/src/test/java/TestFileWatch.java b/modules/server/src/test/java/TestFileWatch.java index cfe866b6f..6f3919fd6 100644 --- a/modules/server/src/test/java/TestFileWatch.java +++ b/modules/server/src/test/java/TestFileWatch.java @@ -20,9 +20,11 @@ * 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. */ +import cn.hutool.core.io.FileUtil; import cn.hutool.core.io.watch.WatchMonitor; import cn.hutool.core.io.watch.WatchUtil; import cn.hutool.core.io.watch.Watcher; +import org.junit.Test; import java.io.File; import java.nio.file.Path; @@ -31,7 +33,39 @@ import java.nio.file.WatchEvent; /** * Created by bwcx_jzy on 2018/10/2. */ + public class TestFileWatch { + + @Test + public void test() { + File file = FileUtil.file("Y:\\Z.package"); + WatchMonitor monitor = WatchUtil.createAll(file, new Watcher() { + @Override + public void onCreate(WatchEvent event, Path currentPath) { + Path context = (Path) event.context(); + System.out.println(context); + System.out.println("创建:" + currentPath); + } + + @Override + public void onModify(WatchEvent event, Path currentPath) { + Path context = (Path) event.context(); + System.out.println("修改:" + currentPath + " " + event.context() + " " + event.kind()); + } + + @Override + public void onDelete(WatchEvent event, Path currentPath) { + System.out.println("删除:" + currentPath); + } + + @Override + public void onOverflow(WatchEvent event, Path currentPath) { + System.out.println("超限:" + currentPath); + } + }); + monitor.run(); + } + public static void main(String[] args) { File file = new File("D:\\SystemDocument\\Desktop\\top.txt"); WatchMonitor watchMonitor = WatchUtil.create(file); diff --git a/modules/server/src/test/java/TestFun.java b/modules/server/src/test/java/TestFun.java index 3e2fcfce0..8cacd415a 100644 --- a/modules/server/src/test/java/TestFun.java +++ b/modules/server/src/test/java/TestFun.java @@ -20,7 +20,9 @@ * 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. */ +import cn.hutool.core.util.StrUtil; import org.junit.Test; +import org.springframework.util.Assert; import java.util.LinkedList; import java.util.List; @@ -34,6 +36,8 @@ public class TestFun { @Test public void tset() { + // Assert.state(!StrUtil.equals("1", "1"), "not equal..."); + Assert.state(StrUtil.equals("1", "1"), "not equal..."); List> AFTER_CALLBACK = new LinkedList<>(); AFTER_CALLBACK.add(() -> { System.out.println("1"); diff --git a/modules/server/src/test/java/TestPath.java b/modules/server/src/test/java/TestPath.java index 56017c994..bf1685304 100644 --- a/modules/server/src/test/java/TestPath.java +++ b/modules/server/src/test/java/TestPath.java @@ -116,4 +116,10 @@ public class TestPath { System.out.println(StrUtil.subBefore("sssddsf", "!", false)); } + + @Test + public void testWinPath(){ + File file = FileUtil.file("/D:/"); + + } } diff --git a/modules/server/src/test/java/TestProcess.java b/modules/server/src/test/java/TestProcess.java new file mode 100644 index 000000000..953e7be0d --- /dev/null +++ b/modules/server/src/test/java/TestProcess.java @@ -0,0 +1,165 @@ +/* + * 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. + */ +import cn.hutool.core.io.IoUtil; +import cn.hutool.core.io.LineHandler; +import cn.hutool.core.io.file.Tailer; +import cn.hutool.core.thread.ThreadUtil; +import cn.hutool.core.util.CharsetUtil; +import cn.hutool.core.util.ReflectUtil; +import org.dromara.jpom.util.CommandUtil; +import org.junit.Test; + +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.atomic.AtomicReference; + +/** + * @author bwcx_jzy + * @since 24/1/3 003 + */ +public class TestProcess { + + @Test + public void testWin() throws IOException { + AtomicReference start = new AtomicReference<>(); + // 执行线程 + Thread thread = new Thread(() -> { + ProcessBuilder processBuilder = new ProcessBuilder(); + processBuilder.redirectErrorStream(true); + processBuilder.command("ping 127.0.0.1 -t".split(" ")); + try { + start.set(processBuilder.start()); + try (InputStream inputStream = start.get().getInputStream()) { + IoUtil.readLines(inputStream, CharsetUtil.CHARSET_GBK, new LineHandler() { + @Override + public void handle(String line) { + throw new IllegalArgumentException(line); + } + }); + } + } catch (IOException e) { + throw new RuntimeException(e); + } + }); + ThreadUtil.execute(thread); + while (true) { + Process process = start.get(); + if (process != null) { + break; + } + // System.out.println("waiting..."); + } + ThreadUtil.sleep(5, TimeUnit.SECONDS); + // 关闭线程 + Thread thread2 = new Thread(() -> { + while (true) { + Process process = start.get(); + if (process.isAlive()) { + process.destroy(); + Object handle = ReflectUtil.getFieldValue(process, "handle"); + System.out.println(handle); + try { + process.waitFor(1, TimeUnit.SECONDS); + } catch (InterruptedException e) { + System.out.println(e.getMessage()); + } + } else { + System.out.println("成功终止"); + break; + } + } + }); + thread2.run(); + } + + @Test + public void testLinux() { + AtomicReference start = new AtomicReference<>(); + AtomicBoolean running = new AtomicBoolean(true); + // 执行线程 + Thread thread = new Thread(() -> { + running.set(true); + try { + ProcessBuilder processBuilder = new ProcessBuilder(); + processBuilder.redirectErrorStream(true); +// String s = "cd /mnt/d/System-Data/Documents/jpom/server/data/build/5a68e19578654cb2965433584ce84f4c/source && mvn clean package"; + String[] command = new String[]{"bash", "/home/user/test.sh"}; +// String[] command = ArrayUtil.append(new String[]{"/bin/bash", "-c"}, s); + System.out.println(Arrays.toString(command)); + processBuilder.command(command); + try { + start.set(processBuilder.start()); + try (InputStream inputStream = start.get().getInputStream()) { + IoUtil.readLines(inputStream, CharsetUtil.CHARSET_UTF_8, new Tailer.ConsoleLineHandler()); + } + int waitFor = start.get().waitFor(); + System.out.println("线程结束:" + waitFor); + } catch (Exception e) { + e.printStackTrace(); + } + } finally { + running.set(false); + } + }); + ThreadUtil.execute(thread); + while (true) { + Process process = start.get(); + if (process != null) { + + break; + } + if (!running.get()) { + System.out.println("线程关闭"); + break; + } + // System.out.println("waiting..."); + } + Process process = start.get(); + if (process == null) { + return; + } + ThreadUtil.sleep(20, TimeUnit.SECONDS); + // 关闭线程 + Thread thread2 = new Thread(() -> { + while (true) { + if (process.isAlive()) { + Object handle = CommandUtil.tryGetProcessId(process); + System.out.println(handle); + process.destroy(); + try { + process.waitFor(1, TimeUnit.SECONDS); + } catch (InterruptedException e) { + System.out.println(e.getMessage()); + } + } else { + System.out.println("成功终止"); + break; + } + } + }); + thread2.run(); + } +} diff --git a/modules/storage-module/pom.xml b/modules/storage-module/pom.xml index fc7975af3..7bf79693d 100644 --- a/modules/storage-module/pom.xml +++ b/modules/storage-module/pom.xml @@ -29,7 +29,7 @@ jpom-parent org.dromara.jpom - 2.10.47 + 2.11.0.8 ../../pom.xml pom @@ -39,7 +39,7 @@ storage-module-mysql 4.0.0 - 2.10.47 + 2.11.0.8 org.dromara.jpom.storage-module jpom-storage-module-parent Jpom storage module diff --git a/modules/storage-module/storage-module-common/pom.xml b/modules/storage-module/storage-module-common/pom.xml index 113b768bb..bd6e0281d 100644 --- a/modules/storage-module/storage-module-common/pom.xml +++ b/modules/storage-module/storage-module-common/pom.xml @@ -30,7 +30,7 @@ org.dromara.jpom.storage-module jpom-storage-module-parent - 2.10.47 + 2.11.0.8 ../pom.xml diff --git a/modules/storage-module/storage-module-common/src/main/java/org/dromara/jpom/db/BaseDbCommonService.java b/modules/storage-module/storage-module-common/src/main/java/org/dromara/jpom/db/BaseDbCommonService.java index 79beb57e6..13778a60d 100644 --- a/modules/storage-module/storage-module-common/src/main/java/org/dromara/jpom/db/BaseDbCommonService.java +++ b/modules/storage-module/storage-module-common/src/main/java/org/dromara/jpom/db/BaseDbCommonService.java @@ -34,6 +34,7 @@ import cn.hutool.db.Page; import cn.hutool.db.PageResult; import cn.hutool.db.ds.DSFactory; import cn.hutool.db.sql.Condition; +import lombok.Getter; import lombok.extern.slf4j.Slf4j; import org.dromara.jpom.model.PageResultDto; import org.dromara.jpom.system.JpomRuntimeException; @@ -69,6 +70,7 @@ public abstract class BaseDbCommonService { /** * 表名 */ + @Getter protected final String tableName; protected final Class tClass; @@ -81,11 +83,7 @@ public abstract class BaseDbCommonService { this.tableName = annotation.value(); } - public String getTableName() { - return tableName; - } - - private DataSource getDataSource() { + protected DataSource getDataSource() { DSFactory dsFactory = StorageServiceFactory.get().getDsFactory(); return dsFactory.getDataSource(); } diff --git a/modules/storage-module/storage-module-common/src/main/java/org/dromara/jpom/db/IStorageService.java b/modules/storage-module/storage-module-common/src/main/java/org/dromara/jpom/db/IStorageService.java index a32623156..0955d3b7b 100644 --- a/modules/storage-module/storage-module-common/src/main/java/org/dromara/jpom/db/IStorageService.java +++ b/modules/storage-module/storage-module-common/src/main/java/org/dromara/jpom/db/IStorageService.java @@ -161,6 +161,13 @@ public interface IStorageService extends AutoCloseable, IMode { */ String dbUrl(); + /** + * 游标查询批处理数量 + * + * @return mysql 和 h2 不一致 + */ + int getFetchSize(); + /** * 异常转换 * diff --git a/modules/storage-module/storage-module-h2/pom.xml b/modules/storage-module/storage-module-h2/pom.xml index 1c195e20c..747af56c6 100644 --- a/modules/storage-module/storage-module-h2/pom.xml +++ b/modules/storage-module/storage-module-h2/pom.xml @@ -30,7 +30,7 @@ org.dromara.jpom.storage-module jpom-storage-module-parent - 2.10.47 + 2.11.0.8 ../pom.xml diff --git a/modules/storage-module/storage-module-h2/src/main/java/org/dromara/jpom/storage/H2StorageServiceImpl.java b/modules/storage-module/storage-module-h2/src/main/java/org/dromara/jpom/storage/H2StorageServiceImpl.java index a23b63e68..adcb6d94f 100644 --- a/modules/storage-module/storage-module-h2/src/main/java/org/dromara/jpom/storage/H2StorageServiceImpl.java +++ b/modules/storage-module/storage-module-h2/src/main/java/org/dromara/jpom/storage/H2StorageServiceImpl.java @@ -72,6 +72,11 @@ public class H2StorageServiceImpl implements IStorageService { return dbUrl; } + @Override + public int getFetchSize() { + return 100; + } + @Override public DbExtConfig.Mode mode() { return DbExtConfig.Mode.H2; diff --git a/modules/storage-module/storage-module-h2/src/main/java/org/dromara/jpom/storage/H2TableBuilderImpl.java b/modules/storage-module/storage-module-h2/src/main/java/org/dromara/jpom/storage/H2TableBuilderImpl.java index 51e423856..c41e40997 100644 --- a/modules/storage-module/storage-module-h2/src/main/java/org/dromara/jpom/storage/H2TableBuilderImpl.java +++ b/modules/storage-module/storage-module-h2/src/main/java/org/dromara/jpom/storage/H2TableBuilderImpl.java @@ -48,13 +48,21 @@ public class H2TableBuilderImpl implements IStorageSqlBuilderService { for (TableViewIndexData viewIndexData : row) { String indexType = viewIndexData.getIndexType(); switch (indexType) { - case "ADD-UNIQUE": + case "ADD-UNIQUE": { // CREATE UNIQUE INDEX IF NOT EXISTS SYSTEMMONITORLOG_INDEX1 ON PUBLIC.SYSTEMMONITORLOG (nodeId, monitorTime); String field = viewIndexData.getField(); List fields = StrUtil.splitTrim(field, "+"); Assert.notEmpty(fields, "索引未配置字段"); stringBuilder.append("CREATE UNIQUE INDEX IF NOT EXISTS ").append(viewIndexData.getName()).append(" ON PUBLIC.").append(viewIndexData.getTableName()).append(" (").append(CollUtil.join(fields, StrUtil.COMMA)).append(")"); break; + } + case "ADD": { + String field = viewIndexData.getField(); + List fields = StrUtil.splitTrim(field, "+"); + Assert.notEmpty(fields, "索引未配置字段"); + stringBuilder.append("CREATE INDEX IF NOT EXISTS ").append(viewIndexData.getName()).append(" ON PUBLIC.").append(viewIndexData.getTableName()).append(" (").append(CollUtil.join(fields, StrUtil.COMMA)).append(")"); + break; + } default: throw new IllegalArgumentException("不支持的类型:" + indexType); } diff --git a/modules/storage-module/storage-module-mysql/README.md b/modules/storage-module/storage-module-mysql/README.md index 4f4851906..13f87d084 100644 --- a/modules/storage-module/storage-module-mysql/README.md +++ b/modules/storage-module/storage-module-mysql/README.md @@ -3,4 +3,12 @@ ```shell docker run -itd --name jpom-mysql-test -p 3306:3306 -e MYSQL_ROOT_PASSWORD=k@4Qoi6qdV#OYjQd mysql +``` + +```shell + mysql -u root -p'k@4Qoi6qdV#OYjQd' +``` + +```shell +create database if not exists jpom default character set utf8mb4; ``` \ No newline at end of file diff --git a/modules/storage-module/storage-module-mysql/pom.xml b/modules/storage-module/storage-module-mysql/pom.xml index 11d57fc48..7c802fe69 100644 --- a/modules/storage-module/storage-module-mysql/pom.xml +++ b/modules/storage-module/storage-module-mysql/pom.xml @@ -30,7 +30,7 @@ org.dromara.jpom.storage-module jpom-storage-module-parent - 2.10.47 + 2.11.0.8 ../pom.xml diff --git a/modules/storage-module/storage-module-mysql/src/main/java/org/dromara/jpom/storage/MysqlStorageServiceImpl.java b/modules/storage-module/storage-module-mysql/src/main/java/org/dromara/jpom/storage/MysqlStorageServiceImpl.java index 0804d0163..682128266 100644 --- a/modules/storage-module/storage-module-mysql/src/main/java/org/dromara/jpom/storage/MysqlStorageServiceImpl.java +++ b/modules/storage-module/storage-module-mysql/src/main/java/org/dromara/jpom/storage/MysqlStorageServiceImpl.java @@ -47,6 +47,11 @@ public class MysqlStorageServiceImpl implements IStorageService { return dbUrl; } + @Override + public int getFetchSize() { + return Integer.MIN_VALUE; + } + @Override public DbExtConfig.Mode mode() { return DbExtConfig.Mode.MYSQL; diff --git a/modules/storage-module/storage-module-mysql/src/main/java/org/dromara/jpom/storage/MysqlTableBuilderImpl.java b/modules/storage-module/storage-module-mysql/src/main/java/org/dromara/jpom/storage/MysqlTableBuilderImpl.java index 410e13e5e..1edf683cd 100644 --- a/modules/storage-module/storage-module-mysql/src/main/java/org/dromara/jpom/storage/MysqlTableBuilderImpl.java +++ b/modules/storage-module/storage-module-mysql/src/main/java/org/dromara/jpom/storage/MysqlTableBuilderImpl.java @@ -48,7 +48,7 @@ public class MysqlTableBuilderImpl implements IStorageSqlBuilderService { for (TableViewIndexData viewIndexData : row) { String indexType = viewIndexData.getIndexType(); switch (indexType) { - case "ADD-UNIQUE": + case "ADD-UNIQUE": { // ALTER TABLE `jpom`.`PROJECT_INFO` //DROP INDEX `workspaceId`, //ADD UNIQUE INDEX `workspaceId`(`workspaceId` ASC, `strike` ASC, `modifyUser`) USING BTREE; @@ -59,6 +59,19 @@ public class MysqlTableBuilderImpl implements IStorageSqlBuilderService { stringBuilder.append(this.delimiter()).append(StrUtil.LF); stringBuilder.append("ALTER TABLE ").append(viewIndexData.getTableName()).append(" ADD UNIQUE INDEX ").append(viewIndexData.getName()).append(" (").append(CollUtil.join(fields, StrUtil.COMMA)).append(")"); break; + } + case "ADD": { + // ALTER TABLE `jpom`.`PROJECT_INFO` + //DROP INDEX `workspaceId`, + //ADD UNIQUE INDEX `workspaceId`(`workspaceId` ASC, `strike` ASC, `modifyUser`) USING BTREE; + String field = viewIndexData.getField(); + List fields = StrUtil.splitTrim(field, "+"); + Assert.notEmpty(fields, "索引未配置字段"); + stringBuilder.append("call drop_index_if_exists('").append(viewIndexData.getTableName()).append("','").append(viewIndexData.getName()).append("')").append(";").append(StrUtil.LF); + stringBuilder.append(this.delimiter()).append(StrUtil.LF); + stringBuilder.append("ALTER TABLE ").append(viewIndexData.getTableName()).append(" ADD INDEX ").append(viewIndexData.getName()).append(" (").append(CollUtil.join(fields, StrUtil.COMMA)).append(")"); + break; + } default: throw new IllegalArgumentException("不支持的类型:" + indexType); } @@ -126,9 +139,9 @@ public class MysqlTableBuilderImpl implements IStorageSqlBuilderService { } // 主键 List primaryKeys = row.stream() - .filter(tableViewData -> tableViewData.getPrimaryKey() != null && tableViewData.getPrimaryKey()) - .map(TableViewRowData::getName) - .collect(Collectors.toList()); + .filter(tableViewData -> tableViewData.getPrimaryKey() != null && tableViewData.getPrimaryKey()) + .map(TableViewRowData::getName) + .collect(Collectors.toList()); Assert.notEmpty(primaryKeys, "表没有主键"); stringBuilder.append(StrUtil.TAB).append("PRIMARY KEY (").append(CollUtil.join(primaryKeys, StrUtil.COMMA)).append(")").append(StrUtil.LF); stringBuilder.append(") ").append("COMMENT=").append("'").append(desc).append("';"); diff --git a/modules/sub-plugin/docker-cli/pom.xml b/modules/sub-plugin/docker-cli/pom.xml index ad1a54854..3a9f48226 100644 --- a/modules/sub-plugin/docker-cli/pom.xml +++ b/modules/sub-plugin/docker-cli/pom.xml @@ -29,7 +29,7 @@ jpom-plugins-parent org.dromara.jpom.plugins - 2.10.47 + 2.11.0.8 ../pom.xml 4.0.0 @@ -80,7 +80,7 @@ org.dromara.jpom.plugins ssh-jsch - ${pom.version} + ${project.version} diff --git a/modules/sub-plugin/docker-cli/src/main/java/org/dromara/jpom/DockerBuild.java b/modules/sub-plugin/docker-cli/src/main/java/org/dromara/jpom/DockerBuild.java index 2a3c4f160..01d1c41dd 100644 --- a/modules/sub-plugin/docker-cli/src/main/java/org/dromara/jpom/DockerBuild.java +++ b/modules/sub-plugin/docker-cli/src/main/java/org/dromara/jpom/DockerBuild.java @@ -70,11 +70,10 @@ public class DockerBuild implements AutoCloseable { public int build() { - File logFile = (File) parameter.get("logFile"); + LogRecorder logRecorder = (LogRecorder) parameter.get("logRecorder"); File tempDir = (File) parameter.get("tempDir"); // 生成临时目录 tempDir = FileUtil.file(tempDir, "docker-temp", IdUtil.fastSimpleUUID()); - LogRecorder logRecorder = LogRecorder.builder().file(logFile).build(); List copy = (List) parameter.get("copy"); String resultFile = (String) parameter.get("resultFile"); String resultFileOut = (String) parameter.get("resultFileOut"); diff --git a/modules/sub-plugin/email/pom.xml b/modules/sub-plugin/email/pom.xml index cd57d86a3..46782defb 100644 --- a/modules/sub-plugin/email/pom.xml +++ b/modules/sub-plugin/email/pom.xml @@ -29,7 +29,7 @@ jpom-plugins-parent org.dromara.jpom.plugins - 2.10.47 + 2.11.0.8 ../pom.xml 4.0.0 diff --git a/modules/sub-plugin/encrypt/pom.xml b/modules/sub-plugin/encrypt/pom.xml index 7ac798fe1..d57185e20 100644 --- a/modules/sub-plugin/encrypt/pom.xml +++ b/modules/sub-plugin/encrypt/pom.xml @@ -29,7 +29,7 @@ jpom-plugins-parent org.dromara.jpom.plugins - 2.10.47 + 2.11.0.8 ../pom.xml 4.0.0 diff --git a/modules/sub-plugin/git-clone/pom.xml b/modules/sub-plugin/git-clone/pom.xml index a23b011fa..4fe5f495b 100644 --- a/modules/sub-plugin/git-clone/pom.xml +++ b/modules/sub-plugin/git-clone/pom.xml @@ -29,7 +29,7 @@ jpom-plugins-parent org.dromara.jpom.plugins - 2.10.47 + 2.11.0.8 ../pom.xml 4.0.0 diff --git a/modules/sub-plugin/pom.xml b/modules/sub-plugin/pom.xml index f9e9eb0f9..e0f592703 100644 --- a/modules/sub-plugin/pom.xml +++ b/modules/sub-plugin/pom.xml @@ -29,7 +29,7 @@ jpom-parent org.dromara.jpom - 2.10.47 + 2.11.0.8 ../../pom.xml pom @@ -43,7 +43,7 @@ ssh-jsch 4.0.0 - 2.10.47 + 2.11.0.8 org.dromara.jpom.plugins jpom-plugins-parent Jpom Plugins diff --git a/modules/sub-plugin/ssh-jsch/pom.xml b/modules/sub-plugin/ssh-jsch/pom.xml index 3ecbfece9..d71757cfc 100644 --- a/modules/sub-plugin/ssh-jsch/pom.xml +++ b/modules/sub-plugin/ssh-jsch/pom.xml @@ -30,7 +30,7 @@ jpom-plugins-parent org.dromara.jpom.plugins - 2.10.47 + 2.11.0.8 ../pom.xml plugin-ssh-jsch diff --git a/modules/sub-plugin/ssh-jsch/src/main/java/org/dromara/jpom/plugins/JschUtils.java b/modules/sub-plugin/ssh-jsch/src/main/java/org/dromara/jpom/plugins/JschUtils.java index 73e4bc5d4..9638b7110 100644 --- a/modules/sub-plugin/ssh-jsch/src/main/java/org/dromara/jpom/plugins/JschUtils.java +++ b/modules/sub-plugin/ssh-jsch/src/main/java/org/dromara/jpom/plugins/JschUtils.java @@ -235,7 +235,7 @@ public class JschUtils { String tempId = IdUtil.fastSimpleUUID(); File tmpDir = FileUtil.getTmpDir(); File buildSsh = FileUtil.file(tmpDir, "ssh_temp", tempId + ".sh"); - try (InputStream sshExecTemplateInputStream = ExtConfigBean.getConfigResourceInputStream("/exec/template.sh")) { + try (InputStream sshExecTemplateInputStream = ExtConfigBean.getConfigResourceInputStream("/ssh/template.sh")) { String sshExecTemplate = IoUtil.readUtf8(sshExecTemplateInputStream); FileUtil.writeString(sshExecTemplate + command + StrUtil.LF, buildSsh, charset); } diff --git a/modules/sub-plugin/svn-clone/pom.xml b/modules/sub-plugin/svn-clone/pom.xml index cb5d0a64b..e18747134 100644 --- a/modules/sub-plugin/svn-clone/pom.xml +++ b/modules/sub-plugin/svn-clone/pom.xml @@ -29,7 +29,7 @@ jpom-plugins-parent org.dromara.jpom.plugins - 2.10.47 + 2.11.0.8 ../pom.xml 4.0.0 diff --git a/modules/sub-plugin/webhook/pom.xml b/modules/sub-plugin/webhook/pom.xml index e00c959e9..1c10a28a5 100644 --- a/modules/sub-plugin/webhook/pom.xml +++ b/modules/sub-plugin/webhook/pom.xml @@ -29,7 +29,7 @@ jpom-plugins-parent org.dromara.jpom.plugins - 2.10.47 + 2.11.0.8 ../pom.xml 4.0.0 diff --git a/modules/sub-plugin/webhook/src/main/java/org/dromara/jpom/webhook/DefaultWebhookPluginImpl.java b/modules/sub-plugin/webhook/src/main/java/org/dromara/jpom/webhook/DefaultWebhookPluginImpl.java index a5efe6df4..3308cafa7 100644 --- a/modules/sub-plugin/webhook/src/main/java/org/dromara/jpom/webhook/DefaultWebhookPluginImpl.java +++ b/modules/sub-plugin/webhook/src/main/java/org/dromara/jpom/webhook/DefaultWebhookPluginImpl.java @@ -43,12 +43,36 @@ import java.util.Map; @Slf4j public class DefaultWebhookPluginImpl implements IDefaultPlugin { + public enum WebhookEvent { + /** + * 构建 + */ + BUILD, + /** + * 项目 + */ + PROJECT, + /** + * 监控 + */ + MONITOR, + /** + * 分发 + */ + DISTRIBUTE, + } + @Override public Object execute(Object main, Map parameter) { String webhook = StrUtil.toStringOrNull(main); if (StrUtil.isEmpty(webhook)) { return null; } + Object jpomWebhookEvent = parameter.remove("JPOM_WEBHOOK_EVENT"); + if (jpomWebhookEvent instanceof WebhookEvent) { + WebhookEvent webhookEvent = (WebhookEvent) jpomWebhookEvent; + log.debug("webhook event: [{}]{}", webhookEvent, webhook); + } try { HttpRequest httpRequest = HttpUtil.createGet(webhook, true); httpRequest.form(parameter); diff --git a/pom.xml b/pom.xml index b1c4037f2..ce58ec093 100644 --- a/pom.xml +++ b/pom.xml @@ -49,7 +49,7 @@ 简而轻的低侵入式在线构建、自动部署、日常运维、项目监控软件 2017 - 2.10.47 + 2.11.0.8 https://gitee.com/dromara/Jpom UTF-8 @@ -61,7 +61,7 @@ true true - 5.8.23 + 5.8.24 2.0.43 2.0.43 5.12.1 diff --git a/script/install-node.sh b/script/install-node.sh index 041198bf8..c9212ab0a 100644 --- a/script/install-node.sh +++ b/script/install-node.sh @@ -24,7 +24,7 @@ # shellcheck disable=SC2016,SC2119,SC2155,SC2206,SC2207,SC2254 -node_version=16.13.1 +node_version=18.19.0 ARCH="$(dpkg --print-architecture)" case "${ARCH}" in diff --git a/script/tag.beta.txt b/script/tag.beta.txt index 7104ad6b3..8053e7678 100644 --- a/script/tag.beta.txt +++ b/script/tag.beta.txt @@ -1 +1 @@ -2.10.47.7 +2.11.0.8 diff --git a/web-vue/.editorconfig b/web-vue/.editorconfig index 72af3e92a..0c5fa3d28 100644 --- a/web-vue/.editorconfig +++ b/web-vue/.editorconfig @@ -12,7 +12,7 @@ trim_trailing_whitespace = true insert_final_newline = true max_line_length = 200 -[*.{json, yml}] +[*.{json,yml}] indent_style = space indent_size = 2 @@ -20,7 +20,7 @@ indent_size = 2 insert_final_newline = false trim_trailing_whitespace = false -[*.vue] +[*.{vue,js,ts}] indent_style = space indent_size = 2 diff --git a/web-vue/.env b/web-vue/.env new file mode 100644 index 000000000..10910ecb8 --- /dev/null +++ b/web-vue/.env @@ -0,0 +1,4 @@ +JPOM_APP_TITLE=Jpom项目运维系统 +JPOM_BASE_URL=./ +JPOM_BASE_API_URL=/api/ +JPOM_PORT=3000 diff --git a/web-vue/.env.dev b/web-vue/.env.dev index 25827f081..21a29ea6c 100644 --- a/web-vue/.env.dev +++ b/web-vue/.env.dev @@ -1,2 +1 @@ -NODE_ENV = 'dev' -proxy_host='127.0.0.1:2122' +JPOM_PROXY_HOST="127.0.0.1:2122" diff --git a/web-vue3/.env.dev b/web-vue/.env.loc similarity index 100% rename from web-vue3/.env.dev rename to web-vue/.env.loc diff --git a/web-vue/.env.production b/web-vue/.env.production index 0a8ab9ccd..5a8235b4c 100644 --- a/web-vue/.env.production +++ b/web-vue/.env.production @@ -1 +1 @@ -NODE_ENV = 'production' +JPOM_BASE_API_URL=/ diff --git a/web-vue3/.eslintignore b/web-vue/.eslintignore similarity index 100% rename from web-vue3/.eslintignore rename to web-vue/.eslintignore diff --git a/web-vue3/.eslintrc.json b/web-vue/.eslintrc.json similarity index 100% rename from web-vue3/.eslintrc.json rename to web-vue/.eslintrc.json diff --git a/web-vue/.gitignore b/web-vue/.gitignore index 63848b70c..8ed362571 100644 --- a/web-vue/.gitignore +++ b/web-vue/.gitignore @@ -1,25 +1,27 @@ -.DS_Store -node_modules -/dist - - -# local env files -.env.local -.env.*.local - -# Log files +# Logs +logs +*.log npm-debug.log* yarn-debug.log* yarn-error.log* pnpm-debug.log* +lerna-debug.log* + +pnpm-lock.yaml + +node_modules +dist +dist-ssr +*.local # Editor directories and files .idea -.vscode +.DS_Store *.suo *.ntvs* *.njsproj *.sln *.sw? -pnpm-lock.yaml +# vite +.vite \ No newline at end of file diff --git a/web-vue/.prettierignore b/web-vue/.prettierignore index a5b65aaac..de4d1f007 100644 --- a/web-vue/.prettierignore +++ b/web-vue/.prettierignore @@ -1,23 +1,2 @@ -**/*.svg -package.json -.umi -.umi-production -/dist -.dockerignore -.DS_Store -.eslintignore -*.png -*.toml -# docker -.editorconfig -Dockerfile* -.gitignore -.prettierignore -LICENSE -.eslintcache -*.lock -yarn-error.log -.history -CNAME -/build -/public \ No newline at end of file +dist +node_modules diff --git a/web-vue3/.prettierrc.json b/web-vue/.prettierrc.json similarity index 100% rename from web-vue3/.prettierrc.json rename to web-vue/.prettierrc.json diff --git a/web-vue3/.vscode/extensions.json b/web-vue/.vscode/extensions.json similarity index 100% rename from web-vue3/.vscode/extensions.json rename to web-vue/.vscode/extensions.json diff --git a/web-vue3/.vscode/settings.json b/web-vue/.vscode/settings.json similarity index 57% rename from web-vue3/.vscode/settings.json rename to web-vue/.vscode/settings.json index 820824e2f..90f770945 100644 --- a/web-vue3/.vscode/settings.json +++ b/web-vue/.vscode/settings.json @@ -7,5 +7,11 @@ "eslint.alwaysShowStatus": true, "prettier.enable": true, "stylelint.enable": true, - "editor.codeActionsOnSave": { "source.fixAll.eslint": true, "source.fixAll.stylelint": true } + "editor.codeActionsOnSave": { + "source.fixAll.eslint": "explicit", + "source.fixAll.stylelint": "explicit" + }, + "[dotenv]": { + "editor.defaultFormatter": "foxundermoon.shell-format" + } } diff --git a/web-vue/README.md b/web-vue/README.md index d476b4685..1fc5c55bf 100644 --- a/web-vue/README.md +++ b/web-vue/README.md @@ -1,91 +1,70 @@ -# JPOM 前端 VUE 项目(server) +## 项目介绍 -## 介绍 +本项目采用 [Vue3](https://cn.vuejs.org/guide/introduction.html#what-is-vue) + [Vite](https://vitejs.dev/) + [TypeScript](https://www.typescriptlang.org/) + [Antdv](https://antdv.com/docs/vue/getting-started-cn) + [Pinia](https://pinia.vuejs.org/)构建。 -这是 Jpom项目的前端部分,基于 [Vue](https://cn.vuejs.org/) 构建,UI框架则是基于 [Ant Design Vue](https://www.antdv.com/docs/vue/introduce-cn/). +项目采用 Vue 3 ` - diff --git a/web-vue/src/api/backup-info.js b/web-vue/src/api/backup-info.ts similarity index 100% rename from web-vue/src/api/backup-info.js rename to web-vue/src/api/backup-info.ts diff --git a/web-vue3/src/api/build-info.ts b/web-vue/src/api/build-info.ts similarity index 81% rename from web-vue3/src/api/build-info.ts rename to web-vue/src/api/build-info.ts index b73e71a8b..440be7052 100644 --- a/web-vue3/src/api/build-info.ts +++ b/web-vue/src/api/build-info.ts @@ -7,7 +7,7 @@ import { loadRouterBase } from './config' * group: 分组名称 * } params */ -export function getBuildList(params: any, loading: boolean) { +export function getBuildList(params, loading) { return axios({ url: '/build/list', method: 'post', @@ -24,7 +24,7 @@ export function getBuildList(params: any, loading: boolean) { * * } params */ -export function getBuildGet(params: any) { +export function getBuildGet(params) { return axios({ url: '/build/get', method: 'get', @@ -48,15 +48,13 @@ export function getBuildGroupAll() { * repositoryId: 仓库id * } params */ -export function getBranchList(params: any) { +export function getBranchList(params) { return axios({ url: '/build/branch-list', method: 'post', timeout: 0, data: params, - headers: { - loadingTip: '正在加载项目分支' - } + headers: {} }) } @@ -75,7 +73,7 @@ export function getBranchList(params: any) { * repostitoryId: 仓库信息 * } params */ -export function editBuild(params: any) { +export function editBuild(params) { const data = { id: params.id, name: params.name, @@ -92,7 +90,8 @@ export function editBuild(params: any) { webhook: params.webhook, autoBuildCron: params.autoBuildCron, buildMode: params.buildMode, - aliasCode: params.aliasCode + aliasCode: params.aliasCode, + resultKeepDay: params.resultKeepDay } return axios({ url: '/build/edit', @@ -105,7 +104,7 @@ export function editBuild(params: any) { * 删除构建信息 * @param {*} id */ -export function deleteBuild(id: string) { +export function deleteBuild(id) { return axios({ url: '/build/delete', method: 'post', @@ -113,11 +112,19 @@ export function deleteBuild(id: string) { }) } +export function deleteatchBuild(data) { + return axios({ + url: '/build/batch-delete', + method: 'post', + data: data + }) +} + /** * 获取触发器地址 * @param {*} id */ -export function getTriggerUrl(data: any) { +export function getTriggerUrl(data) { return axios({ url: '/build/trigger/url', method: 'post', @@ -141,7 +148,7 @@ export function getTriggerUrl(data: any) { * 清理构建 * @param {*} id */ -export function clearBuid(id: string) { +export function clearBuid(id) { return axios({ url: '/build/clean-source', method: 'post', @@ -157,7 +164,7 @@ export function clearBuid(id: string) { * line: 需要获取的行号 1 开始 * } */ -export function loadBuildLog(params: any) { +export function loadBuildLog(params) { return axios({ url: '/build/manage/get-now-log', method: 'post', @@ -173,7 +180,7 @@ export function loadBuildLog(params: any) { * 开始构建 * @param {*} id */ -export function startBuild(data: any) { +export function startBuild(data) { return axios({ url: '/build/manage/start', method: 'post', @@ -185,7 +192,7 @@ export function startBuild(data: any) { * 停止构建 * @param {*} id */ -export function stopBuild(id: string) { +export function stopBuild(id) { return axios({ url: '/build/manage/cancel', method: 'post', @@ -200,7 +207,7 @@ export function stopBuild(id: string) { * status: 状态 * } params */ -export function geteBuildHistory(params: any) { +export function geteBuildHistory(params) { return axios({ url: '/build/history/history_list.json', method: 'post', @@ -212,8 +219,8 @@ export function geteBuildHistory(params: any) { * 下载构建日志 * @param {*} logId */ -export function downloadBuildLog(logId: string) { - return loadRouterBase('/build/history/download_log.html', { +export function downloadBuildLog(logId) { + return loadRouterBase('/build/history/download_log', { logId: logId }) } @@ -222,8 +229,8 @@ export function downloadBuildLog(logId: string) { * 下载构建产物 * @param {*} logId */ -export function downloadBuildFile(logId: string) { - return loadRouterBase('/build/history/download_file.html', { +export function downloadBuildFile(logId) { + return loadRouterBase('/build/history/download_file', { logId: logId }) } @@ -232,7 +239,7 @@ export function downloadBuildFile(logId: string) { * 下载构建产物 * @param {*} logId */ -export function downloadBuildFileByBuild(id: string, numberId: string) { +export function downloadBuildFileByBuild(id, numberId) { return loadRouterBase('/build/history/download_file_by_build', { buildId: id, buildNumberId: numberId @@ -244,7 +251,7 @@ export function downloadBuildFileByBuild(id: string, numberId: string) { * @param {*} logId * @returns */ -export function rollback(logId: string) { +export function rollback(logId) { return axios({ url: '/build/manage/reRelease', method: 'post', @@ -256,7 +263,7 @@ export function rollback(logId: string) { * 删除构建历史记录 * @param {*} logId */ -export function deleteBuildHistory(logId: string) { +export function deleteBuildHistory(logId) { return axios({ url: '/build/history/delete_log.json', method: 'post', @@ -264,7 +271,7 @@ export function deleteBuildHistory(logId: string) { }) } -export function sortItem(params: any) { +export function sortItem(params) { return axios({ url: '/build/sort-item', method: 'get', @@ -309,7 +316,8 @@ export const releaseMethodMap = { export const triggerBuildTypeMap = { 0: '手动', 1: '触发器', - 2: '定时' + 2: '定时', + 3: '手动回滚' } export const buildModeMap = { diff --git a/web-vue/src/api/build.js b/web-vue/src/api/build.ts similarity index 100% rename from web-vue/src/api/build.js rename to web-vue/src/api/build.ts diff --git a/web-vue/src/api/command.js b/web-vue/src/api/command.ts similarity index 100% rename from web-vue/src/api/command.js rename to web-vue/src/api/command.ts diff --git a/web-vue/src/api/common.js b/web-vue/src/api/common.js deleted file mode 100644 index 0151a84a4..000000000 --- a/web-vue/src/api/common.js +++ /dev/null @@ -1,12 +0,0 @@ -import axios from "./config"; - -/** - * 生成分片上传 id - */ -export function generateShardingId() { - return axios({ - url: "/generate-sharding-id", - method: "get", - data: {}, - }); -} diff --git a/web-vue3/src/api/common.ts b/web-vue/src/api/common.ts similarity index 100% rename from web-vue3/src/api/common.ts rename to web-vue/src/api/common.ts diff --git a/web-vue3/src/api/config.ts b/web-vue/src/api/config.ts similarity index 80% rename from web-vue3/src/api/config.ts rename to web-vue/src/api/config.ts index c7e6a8a5b..4449526d1 100644 --- a/web-vue3/src/api/config.ts +++ b/web-vue/src/api/config.ts @@ -4,8 +4,9 @@ import { NO_NOTIFY_KEY, TOKEN_HEADER_KEY, CACHE_WORKSPACE_ID } from '@/utils/con import { refreshToken } from './user/user' import { useAppStore } from '@/stores/app' import { useUserStore } from '@/stores/user' -import { useMenuStore } from '@/stores/menu' +import Qs from 'qs' +import router from '../router' const delTimeout: number = 20 * 1000 const apiTimeout: number = Number(jpomWindow.apiTimeout === '' ? delTimeout : jpomWindow.apiTimeout) // debug routerBase @@ -13,9 +14,11 @@ const routerBase: string = jpomWindow.routerBase === '' ? '' : jpomW const pro: boolean = process.env.NODE_ENV === 'production' +const baseURL = import.meta.env.JPOM_BASE_API_URL + // 创建实例 const instance: AxiosInstance = axios.create({ - baseURL: import.meta.env.JPOM_BASE_API_URL, + baseURL: baseURL, timeout: apiTimeout || delTimeout, headers: { @@ -24,6 +27,8 @@ const instance: AxiosInstance = axios.create({ responseType: 'json' }) +let refreshTokenIng = false + // 请求拦截 instance.interceptors.request.use((config: InternalAxiosRequestConfig) => { const appStore = useAppStore() @@ -31,7 +36,7 @@ instance.interceptors.request.use((config: InternalAxiosRequestConfig) => { const { headers } = config headers[TOKEN_HEADER_KEY] = userStore.token - headers[CACHE_WORKSPACE_ID] = appStore.getWorkspaceId + headers[CACHE_WORKSPACE_ID] = appStore.getWorkspaceId() if (routerBase) { // 防止 url 出现 // @@ -101,11 +106,16 @@ async function request(arg: string | AxiosRequestConfig, config?: Axios // 需要续签 if (data.code === 801) { + if (refreshTokenIng) { + return Promise.reject(data) + } $notification.info({ message: '登录信息过期,尝试自动续签...', description: '如果不需要自动续签,请修改配置文件。该续签将不会影响页面。' }) + refreshTokenIng = true await redoRequest(response.config) + refreshTokenIng = false return Promise.reject(data) } @@ -121,7 +131,7 @@ async function request(arg: string | AxiosRequestConfig, config?: Axios message: '禁止访问', description: '禁止访问,当前IP限制访问' }) - window.location.href = jpomWindow.routerBase + '/system/ipAccess' + window.location.href = jpomWindow.routerBase + '/prohibit-access' return Promise.reject(data) } @@ -147,12 +157,17 @@ async function redoRequest(config: AxiosRequestConfig) { if (result.code === 200) { // 调用 store action 存储当前登录的用户名和 token const userStore = useUserStore() - const menuStore = useMenuStore() await userStore.login(result.data) - await menuStore.loadSystemMenus() - await request(config) - return result + // 刷新页面 + $notification.success({ + message: '提示', + description: '自动续签成功,页面将在 2 秒后自动刷新' + }) + setTimeout(() => { + location.reload() + }, 2000) + //return await request(config) } return Promise.reject() } @@ -160,32 +175,37 @@ async function redoRequest(config: AxiosRequestConfig) { function toLogin(res: IResponse, response: AxiosResponse>) { $notification.warn({ message: '提示信息 ' + (pro ? '' : response.config.url), - description: res.msg + description: res.msg, + key: 'to-login' }) const userStore = useUserStore() userStore.logOut().then(() => { - // const index = location.hash.indexOf("?"); - // let params = {}; - // if (index > -1) { - // params = Qs.parse(location.hash.substring(index + 1)); - // } - // router.push({ - // path: "/login", - // query: params, - // }); + const index = location.hash.indexOf('?') + let params = {} + if (index > -1) { + params = Qs.parse(location.hash.substring(index + 1)) + } + const pageUrl = router.resolve({ + path: '/login', + query: params + }) + + setTimeout(() => { + ;(location.href as any) = pageUrl.href + }, 2000) }) return false } export function loadRouterBase(url: string, params: any) { const paramsObj = params || {} - paramsObj[CACHE_WORKSPACE_ID] = useAppStore().getWorkspaceId + paramsObj[CACHE_WORKSPACE_ID] = useAppStore().getWorkspaceId() let queryStr = '' Object.keys(paramsObj).forEach((key, i) => { queryStr += `${i === 0 ? '' : '&'}${key}=${paramsObj[key]}` }) - return `${((routerBase || '') + url).replace(new RegExp('//', 'gm'), '/')}?${queryStr}` + return `${((baseURL + routerBase || '') + url).replace(new RegExp('//', 'gm'), '/')}?${queryStr}` } /** @@ -196,7 +216,6 @@ export function loadRouterBase(url: string, params: any) { */ export function getWebSocketUrl(url: string, parameter: any) { const protocol: string = location.protocol === 'https:' ? 'wss://' : 'ws://' - const domain: string = jpomWindow.routerBase - const fullUrl: string = (domain + url).replace(new RegExp('//', 'gm'), '/') + const fullUrl: string = (baseURL + routerBase + url).replace(new RegExp('//', 'gm'), '/') return `${protocol}${location.host}${fullUrl}?${parameter}` } diff --git a/web-vue/src/api/dispatch.js b/web-vue/src/api/dispatch.ts similarity index 89% rename from web-vue/src/api/dispatch.js rename to web-vue/src/api/dispatch.ts index 9817f2576..1112a949c 100644 --- a/web-vue/src/api/dispatch.js +++ b/web-vue/src/api/dispatch.ts @@ -71,7 +71,7 @@ export function editDispatch(params) { * runMode: 运行方式 * mainClass: 启动类 * javaExtDirsCp: 目录地址 - * whitelistDirectory: 白名单地址 + * whitelistDirectory: 授权地址 * lib: lib * add_xxx: xxx 表示添加的节点信息 @@ -136,6 +136,30 @@ export function remoteDownload(params) { }); } +export function useBuild(params) { + return axios({ + url: "/outgiving/use-build", + method: "post", + data: params, + }); +} + +export function useuseFileStorage(params) { + return axios({ + url: "/outgiving/use-file-storage", + method: "post", + data: params, + }); +} + +export function useuseStaticFileStorage(params) { + return axios({ + url: "/outgiving/use-static-file-storage", + method: "post", + data: params, + }); +} + /** * 释放分发 * @param {*} id 分发 ID @@ -189,7 +213,7 @@ export function getDishPatchLogList(params) { }); } -// 获取分发白名单数据 +// 获取分发授权数据 export function getDispatchWhiteList(params) { return axios({ url: "/outgiving/white-list", @@ -199,7 +223,7 @@ export function getDispatchWhiteList(params) { } /** - * 编辑分发白名单 + * 编辑分发授权 * @param {*} params */ export function editDispatchWhiteList(params) { @@ -210,18 +234,6 @@ export function editDispatchWhiteList(params) { }); } -/** - * 编辑分发白名单 - * @param {*} params - */ -export function editDispatchWhiteList2(params) { - return axios({ - url: "/outgiving/whitelist-directory-submit2", - method: "post", - data: params, - }); -} - /** * 取消分发 * @param {*} id 分发 ID @@ -282,3 +294,10 @@ export const statusMap = { 3: "取消分发", 4: "分发失败", }; + +export const dispatchMode = { + upload: "手动上传", + download: "远程下载", + "build-trigger": "构建触发", + "use-build": "构建产物", +}; diff --git a/web-vue3/src/api/docker-api.ts b/web-vue/src/api/docker-api.ts similarity index 77% rename from web-vue3/src/api/docker-api.ts rename to web-vue/src/api/docker-api.ts index 745a9f4a7..d3f41618a 100644 --- a/web-vue3/src/api/docker-api.ts +++ b/web-vue/src/api/docker-api.ts @@ -1,10 +1,11 @@ import axios from './config' +import { loadRouterBase } from './config' /** * 容器列表 * @param {JSON} params */ -export function dockerList(params: any) { +export function dockerList(params) { return axios({ url: '/docker/list', method: 'post', @@ -24,7 +25,7 @@ export function apiVersions() { }) } -export function editDocker(data: any) { +export function editDocker(data) { return axios({ url: '/docker/edit', method: 'post', @@ -38,7 +39,7 @@ export function editDocker(data: any) { * id: docker ID * } params */ -export function deleteDcoker(params: any) { +export function deleteDcoker(params) { return axios({ url: '/docker/del', method: 'get', @@ -193,6 +194,12 @@ export function dockerUpdateContainer(urlPrefix, params) { }) } +export function dockerContainerDownloaLog(urlPrefix, id) { + return loadRouterBase(urlPrefix + '/container/download-log', { + id: id + }) +} + /** * 容器中的镜像列表 * @param {JSON} params @@ -217,6 +224,18 @@ export function dockerImageRemove(urlPrefix, params) { }) } +/** + * 批量删除镜像 + * @param {JSON} params + */ +export function dockerImageBatchRemove(urlPrefix, params) { + return axios({ + url: urlPrefix + '/images/batchRemove', + method: 'get', + params: params + }) +} + /** * inspect 镜像 * @param {JSON} params @@ -256,6 +275,35 @@ export function dockerImagePullImage(urlPrefix, params) { }) } +/** + * 导出镜像 + * @param {JSON} params + */ +export function dockerImageSaveImage(urlPrefix, params) { + return loadRouterBase(urlPrefix + '/images/save-image', params) +} + +/** + * 导入镜像到容器 节点 + * @param { + * file: 文件 multipart/form-data, + * id: 容器ID, + * + * } formData + */ +export function dockerImageLoadImage(baseUrl, formData) { + return axios({ + url: baseUrl + '/images/load-image', + headers: { + 'Content-Type': 'multipart/form-data;charset=UTF-8' + }, + method: 'post', + // 0 表示无超时时间 + timeout: 0, + data: formData + }) +} + /** * 拉取镜像日志 * @param {JSON} params @@ -308,10 +356,33 @@ export function dockerNetworksList(urlPrefix, params) { }) } -export function syncToWorkspace(params: any) { +export function syncToWorkspace(params) { return axios({ url: '/docker/sync-to-workspace', method: 'get', params: params }) } + +export function dockerAllTag(params) { + return axios({ + url: '/docker/all-tag', + method: 'get', + params: params + }) +} + +/** + * 容器 重建容器 + * @param {JSON} params + */ +export function dockerContainerRebuildContainer(urlPrefix, params) { + return axios({ + url: urlPrefix + '/container/rebuild-container', + method: 'post', + headers: { + 'Content-Type': 'application/json' + }, + data: params + }) +} diff --git a/web-vue/src/api/docker-swarm.js b/web-vue/src/api/docker-swarm.ts similarity index 100% rename from web-vue/src/api/docker-swarm.js rename to web-vue/src/api/docker-swarm.ts diff --git a/web-vue/src/api/ext-config.js b/web-vue/src/api/ext-config.ts similarity index 100% rename from web-vue/src/api/ext-config.js rename to web-vue/src/api/ext-config.ts diff --git a/web-vue/src/api/external.js b/web-vue/src/api/external.js deleted file mode 100644 index 615eb6473..000000000 --- a/web-vue/src/api/external.js +++ /dev/null @@ -1,25 +0,0 @@ -import axios from "axios"; - -const external = axios.create({ - timeout: 5 * 1000, - headers: {}, -}); - -// 响应拦截器 -external.interceptors.response.use( - async (response) => { - return response.data; - }, - (error) => { - console.error(error); - return Promise.reject(error); - } -); - -export function executionRequest(url, param) { - return external({ - url: url, - method: "get", - params: param, - }); -} diff --git a/web-vue3/src/api/external.ts b/web-vue/src/api/external.ts similarity index 80% rename from web-vue3/src/api/external.ts rename to web-vue/src/api/external.ts index 6ac34af53..3216cba77 100644 --- a/web-vue3/src/api/external.ts +++ b/web-vue/src/api/external.ts @@ -11,12 +11,11 @@ external.interceptors.response.use( return response.data }, (error) => { - console.error(error) return Promise.reject(error) } ) -export function executionRequest(url: string, param: any) { +export function executionRequest(url: any, param: any) { return external({ url: url, method: 'get', diff --git a/web-vue/src/api/file-manager/file-storage.js b/web-vue/src/api/file-manager/file-storage.ts similarity index 98% rename from web-vue/src/api/file-manager/file-storage.js rename to web-vue/src/api/file-manager/file-storage.ts index 2a541d6de..6253b26ab 100644 --- a/web-vue/src/api/file-manager/file-storage.js +++ b/web-vue/src/api/file-manager/file-storage.ts @@ -60,7 +60,6 @@ export function hasFile(params) { }); } -// 判断文件是否存在 export function delFile(params) { return axios({ url: "/file-storage/del", diff --git a/web-vue/src/api/file-manager/release-task-log.js b/web-vue/src/api/file-manager/release-task-log.ts similarity index 100% rename from web-vue/src/api/file-manager/release-task-log.js rename to web-vue/src/api/file-manager/release-task-log.ts diff --git a/web-vue/src/api/file-manager/static-storage.ts b/web-vue/src/api/file-manager/static-storage.ts new file mode 100644 index 000000000..035b22020 --- /dev/null +++ b/web-vue/src/api/file-manager/static-storage.ts @@ -0,0 +1,51 @@ +import axios from "@/api/config"; + +export function staticFileStorageList(params) { + return axios({ + url: "/file-storage/static/list", + method: "post", + data: params, + }); +} + +export function delFile(params) { + return axios({ + url: "/file-storage/static/del", + method: "get", + params: params, + }); +} + +// 下载 url +export function triggerUrl(params) { + return axios({ + url: "/file-storage/static/trigger-url", + method: "get", + params: params, + }); +} + +// 修改文件 +export function fileEdit(params) { + return axios({ + url: "/file-storage/static/edit", + method: "post", + data: params, + }); +} + +export function hasStaticFile(params) { + return axios({ + url: "/file-storage/static/has-file", + method: "get", + params: params, + }); +} + +export function staticScanner(params) { + return axios({ + url: "/file-storage/static/scanner", + method: "get", + params: params, + }); +} diff --git a/web-vue/src/api/install.js b/web-vue/src/api/install.ts similarity index 100% rename from web-vue/src/api/install.js rename to web-vue/src/api/install.ts diff --git a/web-vue/src/api/log-read.js b/web-vue/src/api/log-read.ts similarity index 100% rename from web-vue/src/api/log-read.js rename to web-vue/src/api/log-read.ts diff --git a/web-vue/src/api/menu.js b/web-vue/src/api/menu.ts similarity index 100% rename from web-vue/src/api/menu.js rename to web-vue/src/api/menu.ts diff --git a/web-vue/src/api/monitor.js b/web-vue/src/api/monitor.ts similarity index 100% rename from web-vue/src/api/monitor.js rename to web-vue/src/api/monitor.ts diff --git a/web-vue/src/api/node-other.js b/web-vue/src/api/node-other.ts similarity index 85% rename from web-vue/src/api/node-other.js rename to web-vue/src/api/node-other.ts index 9e87c244b..af760cc7a 100644 --- a/web-vue/src/api/node-other.js +++ b/web-vue/src/api/node-other.ts @@ -2,18 +2,6 @@ import axios from "./config"; /************************** */ -/** - * script 列表 - * @param {String} nodeId 节点 ID - */ -export function getScriptList(params) { - return axios({ - url: "/node/script/list", - method: "post", - data: params, - }); -} - /** * script 服务端中的所有列表 */ @@ -119,15 +107,6 @@ export function deleteScript(params) { }); } -// 删除节点脚本模版缓存 -export function delAllCache() { - return axios({ - url: "/node/script/clear_all", - method: "get", - params: {}, - }); -} - /** * 解绑 Script * @param {id} params diff --git a/web-vue/src/api/node-project-backup.js b/web-vue/src/api/node-project-backup.ts similarity index 100% rename from web-vue/src/api/node-project-backup.js rename to web-vue/src/api/node-project-backup.ts diff --git a/web-vue/src/api/node-project.ts b/web-vue/src/api/node-project.ts new file mode 100644 index 000000000..1110b8bb1 --- /dev/null +++ b/web-vue/src/api/node-project.ts @@ -0,0 +1,558 @@ +/** + * 节点管理 api + */ +import axios from "./config"; +import { loadRouterBase } from "./config"; + +/** + * 项目列表 + * @param {JSON} params { + * nodeId: 节点 ID + * group: 分组名称 + * } + */ +export function getProjectList(params) { + return axios({ + url: "/node/manage/get_project_info", + method: "post", + data: params, + }); +} + +/** + * 项目运行信息,返回项目占用端口和 pid + * @param {JSON} params { + * nodeId: 节点 ID + * ids: 项目 ID 数组字符串格式 ["id1", "id2"] + * } + */ +export function getRuningProjectInfo(params, noTip) { + return axios({ + url: "/node/manage/getProjectPort", + method: "post", + data: params, + timeout: 0, + headers: { + loading: "no", + tip: noTip ? "no" : "", + }, + }); +} + +/** + * 获取单个项目信息 + * @param { + * nodeId: 节点 ID + * id: 项目 ID + * } params + */ +export function getProjectData(params, loading) { + return axios({ + url: "/node/manage/getProjectData.json", + method: "post", + data: params, + headers: { + loading: loading === false ? "no" : "", + }, + }); +} + +/** + * 项目授权列表 + * @param {String} nodeId 节点 ID + */ +export function getProjectAccessList(nodeId) { + return axios({ + url: "/node/manage/project-access-list", + method: "post", + data: { nodeId }, + }); +} + +/** + * 编辑项目 + * @param {JSON} params { + * nodeId: 节点 ID + * id: 项目 ID + * name: 项目名称 + * runMode: 运行方式 + * whitelistDirectory: 项目授权路径 + * lib: 项目文件夹 + * group: 分组名称 + * ... + * } + + */ +export function editProject(params) { + const data = { + nodeId: params.nodeId, + id: params.id, + name: params.name, + group: params.group, + runMode: params.runMode, + whitelistDirectory: params.whitelistDirectory, + lib: params.lib, + mainClass: params.mainClass, + javaExtDirsCp: params.javaExtDirsCp, + jvm: params.jvm, + args: params.args, + javaCopyIds: params.javaCopyIds, + token: params.token, + logPath: params.logPath, + autoStart: params.autoStart, + dslContent: params.dslContent, + dslEnv: params.dslEnv, + linkId: params.linkId, + }; + return axios({ + url: "/node/manage/saveProject", + method: "post", + data, + }); +} + +/** + * 删除项目 + * @param { + * nodeId: 节点 ID + * id: 项目 ID + + * } params + */ +export function deleteProject(params) { + return axios({ + url: "/node/manage/deleteProject", + method: "post", + data: params, + }); +} + +export function migrateWorkspace(params) { + return axios({ + url: "/node/manage/migrate-workspace", + method: "post", + data: params, + }); +} + +export function releaseOutgiving(params) { + return axios({ + url: "/node/manage/release-outgiving", + method: "post", + data: params, + }); +} + +/** + * 项目文件列表 + * @param { + * nodeId: 节点 ID + * id: 项目 ID + * } params + */ +export function getFileList(params) { + return axios({ + url: "/node/manage/file/getFileList", + method: "post", + headers: { + loading: "no", + }, + data: params, + }); +} + +/** + * 下载项目文件 + * @param { + * nodeId: 节点 ID + * id: 项目 ID + * levelName: 文件 levelName + * filename: 文件名称 + * } params + */ +export function downloadProjectFile(params) { + return loadRouterBase("/node/manage/file/download", params); + // return axios({ + // url: "/node/manage/file/download", + // method: "get", + // responseType: "blob", + // timeout: 0, + // params, + // }); +} + +export function readFile(formData) { + return axios({ + url: "/node/manage/file/read_file", + method: "get", + params: formData, + }); +} + +export function remoteDownload(formData) { + return axios({ + url: "/node/manage/file/remote_download", + method: "get", + timeout: 0, + params: formData, + }); +} + +export function updateFile(formData) { + return axios({ + url: "/node/manage/file/update_config_file", + method: "post", + data: formData, + }); +} + +/** + * 上传项目文件 + * @param { + * file: 文件 multipart/form-data + * nodeId: 节点 ID + * id: 项目 ID + * levelName: 目录地址 + * type: unzip 表示压缩文件 *上传压缩文件时需要 + * clearType: {clear: 清空文件夹, noClear: 不清空} *上传压缩文件时需要 + * } formData + */ +export function uploadProjectFile(formData) { + return axios({ + url: "/node/manage/file/upload-sharding", + headers: { + "Content-Type": "multipart/form-data;charset=UTF-8", + loading: "no", + }, + method: "post", + // 0 表示无超时时间 + timeout: 0, + data: formData, + }); +} + +/** + * 合并分片项目文件 + * @param { + * file: 文件 multipart/form-data + * nodeId: 节点 ID + * id: 项目 ID + * levelName: 目录地址 + * type: unzip 表示压缩文件 *上传压缩文件时需要 + * clearType: {clear: 清空文件夹, noClear: 不清空} *上传压缩文件时需要 + * } formData + */ +export function shardingMerge(formData) { + return axios({ + url: "/node/manage/file/sharding-merge", + headers: {}, + method: "post", + // 0 表示无超时时间 + timeout: 0, + data: formData, + }); +} + +/** + * 删除文件 + * @param { + * nodeId: 节点 ID + * id: 项目 ID + * levelName: 文件 levelName + * filename: 文件名称 + * type: 操作类型 {clear: 清空, noclear: 不清空} 填入此参数可以忽略 levelName 和 filename 参数 + * } params + */ +export function deleteProjectFile(params) { + return axios({ + url: "/node/manage/file/deleteFile", + method: "post", + data: params, + }); +} + +/** + * 获取项目日志文件大小 + * @param { + * nodeId: 节点 ID + * id: 项目 ID + + * } params + */ +export function getProjectLogSize(params) { + return axios({ + url: "/node/manage/log/logSize", + method: "post", + data: params, + headers: { + loading: "no", + }, + }); +} + +/** + * 下载项目日志文件 + * @param { + * nodeId: 节点 ID + * id: 项目 ID + + * } params + */ +export function downloadProjectLogFile(params) { + return loadRouterBase("/node/manage/log/export", params); +} + +/** + * 项目日志备份列表 + * @param { + * nodeId: 节点 ID + * id: 项目 ID + * } params + */ +export function getLogBackList(params) { + return axios({ + url: "/node/manage/log/log-back-list", + method: "post", + data: params, + }); +} + +/** + * 项目日志备份文件下载 + * @param { + * nodeId: 节点 ID + * id: 项目 ID + + * key: 文件名 + * } params + */ +export function downloadProjectLogBackFile(params) { + return loadRouterBase("/node/manage/log/logBack_download", params); +} + +/** + * 项目日志备份文件删除 + * @param { + * nodeId: 节点 ID + * id: 项目 ID + + * name: 文件名 + * } params + */ +export function deleteProjectLogBackFile(params) { + return axios({ + url: "/node/manage/log/logBack_delete", + method: "post", + data: params, + }); +} + +/** + * 获取内存信息接口 + * @param { + * nodeId: 节点 ID + * tag: 项目 ID + + * } params + */ +export function getInternalData(params) { + return axios({ + url: "/node/manage/getInternalData", + method: "post", + timeout: 0, + data: params, + }); +} + +// /** +// * 查看线程 +// * @param { +// * nodeId: 节点 ID +// * } params +// */ +// export function getThreadInfo(params) { +// return axios({ +// url: "/node/manage/threadInfos", +// method: "post", +// timeout: 0, +// data: params, +// }); +// } + +// /** +// * 导出堆栈信息 +// * @param { +// * nodeId: 节点 ID +// * tag: 项目 ID +// * } params +// */ +// export function exportStack(params) { +// return axios({ +// url: "/node/manage/stack", +// method: "get", +// responseType: "blob", +// timeout: 0, +// params, +// }); +// } + +// /** +// * 导出内存信息 +// * @param { +// * nodeId: 节点 ID +// * tag: 项目 ID +// * } params +// */ +// export function exportRam(params) { +// return axios({ +// url: "/node/manage/ram", +// method: "get", +// responseType: "blob", +// timeout: 0, +// params, +// }); +// } + +// /** +// * 查询节点目录是否存在 +// * @param { +// * nodeId: 节点 ID, +// * newLib: 新目录地址 +// * } params +// */ +// export function nodeJudgeLibExist(params) { +// return axios({ +// url: "/node/manage/judge_lib.json", +// method: "post", +// data: params, +// headers: { +// tip: "no", +// }, +// }); +// } + +/** + * 操作项目 + * @param { + * nodeId: 节点 ID, + * id: 项目id + * } params + */ +export function operateProject(params) { + return axios({ + url: "/node/manage/operate", + method: "post", + data: params, + headers: { + loading: "no", + tip: "no", + }, + }); +} + +/** + * 获取触发器地址 + * @param {*} id + */ +export function getProjectTriggerUrl(data) { + return axios({ + url: "/node/project-trigger-url", + method: "post", + data: data, + }); +} + +/** + * 新增目录 或文件 + * @param params + * @returns {id, path, name,unFolder} params x + */ +export function newFileFolder(params) { + return axios({ + url: "/node/manage/file/new_file_folder", + method: "get", + params, + }); +} + +/** + * 修改目录或文件名称 + * @param params + * @returns {id, levelName, filename,newname} params x + */ +export function renameFileFolder(params) { + return axios({ + url: "/node/manage/file/rename_file_folder", + method: "get", + params, + }); +} + +/** + * 构建分组 + */ +export function getProjectGroupAll() { + return axios({ + url: "/node/list-project-group-all", + method: "get", + }); +} + +/** + * 所有的运行模式 + */ +export const runModeList = ["Dsl", "ClassPath", "Jar", "JarWar", "JavaExtDirsCp", "File", "Link"]; + +export const runModeArray = [ + { name: "Dsl", desc: "自定义脚本项目(python、nodejs、go、接口探活、es)【推荐】" }, + { name: "ClassPath", desc: "Java 项目(java -classpath)" }, + { name: "Jar", desc: "Java 项目(java -jar xxx)" }, + { name: "JavaExtDirsCp", desc: "Java 项目(java -Djava.ext.dirs=lib -cp conf:run.jar $MAIN_CLASS)" }, + { name: "File", desc: "静态文件项目(前端、日志等)" }, + { + name: "Link", + desc: "软链项目(类似于项目副本使用相关路径的文件)", + // 仅有节点有此项目(节点分发不支持) + onlyNode: true, + }, + { name: "JarWar", desc: "Java 项目(java -jar Springboot war)【不推荐】" }, +]; + +/** + * java 项目的运行模式 + */ +export const javaModes = ["ClassPath", "Jar", "JarWar", "JavaExtDirsCp"]; + +/** + * 有状态管理的运行模式 + */ +export const noFileModes = ["ClassPath", "Jar", "JarWar", "JavaExtDirsCp", "Dsl", "Link"]; + +/* + * 下载导入模板 + * + */ +export function importTemplate(data) { + return loadRouterBase("/node/manage/import-template", data); +} + +/* + * 导出数据 + * + */ +export function exportData(data) { + return loadRouterBase("/node/manage/export-data", data); +} +// 导入数据 +export function importData(formData) { + return axios({ + url: "/node/manage/import-data", + headers: { + "Content-Type": "multipart/form-data;charset=UTF-8", + }, + method: "post", + // 0 表示无超时时间 + timeout: 0, + data: formData, + }); +} diff --git a/web-vue/src/api/node-script.js b/web-vue/src/api/node-script.js deleted file mode 100644 index e69de29bb..000000000 diff --git a/web-vue3/src/api/node-script.ts b/web-vue/src/api/node-script.ts similarity index 100% rename from web-vue3/src/api/node-script.ts rename to web-vue/src/api/node-script.ts diff --git a/web-vue3/src/api/node-stat.ts b/web-vue/src/api/node-stat.ts similarity index 90% rename from web-vue3/src/api/node-stat.ts rename to web-vue/src/api/node-stat.ts index ac40a369b..9331fe3f8 100644 --- a/web-vue3/src/api/node-stat.ts +++ b/web-vue/src/api/node-stat.ts @@ -1,9 +1,9 @@ import axios from './config' import { parseTime, formatPercent2, renderSize, formatDuration } from '@/utils/const' -// import echarts from "echarts"; +import * as echarts from 'echarts' // 获取机器信息 -export function machineInfo(params: any) { +export function machineInfo(params) { return axios({ url: '/node/machine-info', method: 'get', @@ -15,7 +15,7 @@ export function machineInfo(params: any) { } // 机器文件系统 -export function machineDiskInfo(params: any) { +export function machineDiskInfo(params) { return axios({ url: '/node/disk-info', method: 'get', @@ -27,7 +27,7 @@ export function machineDiskInfo(params: any) { } // 机器硬件硬盘 -export function machineHwDiskInfo(params: any) { +export function machineHwDiskInfo(params) { return axios({ url: '/node/hw-disk-info', method: 'get', @@ -78,7 +78,7 @@ const defaultData = { * @param { JSON } data * @returns */ -export function generateNodeTopChart(params: any) { +export function generateNodeTopChart(data) { const cpuItem = { name: 'cpu占用', type: 'line', @@ -152,7 +152,7 @@ export function generateNodeTopChart(params: any) { tooltip: { trigger: 'axis', show: true, - formatter: function (params: any) { + formatter: function (params) { var html = params[0].name + '
' for (var i = 0; i < params.length; i++) { html += params[i].marker + params[i].seriesName + ':' + formatPercent2(params[i].value) + '
' @@ -169,7 +169,7 @@ export function generateNodeTopChart(params: any) { * @param { JSON } data * @returns */ -export function generateNodeNetChart(params: any) { +export function generateNodeNetChart(data) { const rxItem = { name: '接收', type: 'line', @@ -221,7 +221,7 @@ export function generateNodeNetChart(params: any) { tooltip: { trigger: 'axis', show: true, - formatter: function (params: any) { + formatter: function (params) { var html = params[0].name + '
' for (var i = 0; i < params.length; i++) { html += params[i].marker + params[i].seriesName + ':' + renderSize(params[i].value) + '/s
' @@ -238,7 +238,7 @@ export function generateNodeNetChart(params: any) { * @param { JSON } data * @returns */ -export function generateNodeNetworkTimeChart(params: any) { +export function generateNodeNetworkTimeChart(data) { const dataArray = { name: '网络延迟', type: 'line', @@ -279,7 +279,7 @@ export function generateNodeNetworkTimeChart(params: any) { tooltip: { trigger: 'axis', show: true, - formatter: function (params: any) { + formatter: function (params) { var html = params[0].name + '
' for (var i = 0; i < params.length; i++) { html += params[i].marker + params[i].seriesName + ':' + formatDuration(params[i].value) + '
' @@ -303,11 +303,15 @@ export function drawChart(data, domId, parseFn) { return } const option = parseFn(data) - + let myChart = echarts.getInstanceByDom(historyChartDom) + if (myChart) { + myChart.setOption(option) + return myChart + } // 绘制图表 - // const historyChart = echarts.init(historyChartDom); - // historyChart.setOption(option); - // return historyChart; + const historyChart = echarts.init(historyChartDom) + historyChart.setOption(option) + return historyChart } // export const status = { @@ -319,7 +323,7 @@ export function drawChart(data, domId, parseFn) { // }; // 机器网络 -export function machineNetworkInterfaces(params: any) { +export function machineNetworkInterfaces(params) { return axios({ url: '/node/network-interfaces', method: 'get', diff --git a/web-vue/src/api/node-system.js b/web-vue/src/api/node-system.ts similarity index 100% rename from web-vue/src/api/node-system.js rename to web-vue/src/api/node-system.ts diff --git a/web-vue/src/api/node.ts b/web-vue/src/api/node.ts new file mode 100644 index 000000000..0f14ce5d7 --- /dev/null +++ b/web-vue/src/api/node.ts @@ -0,0 +1,321 @@ +import axios from "./config"; + +// node 列表 +export function getNodeList(params) { + return axios({ + url: "/node/list_data.json", + method: "post", + data: params, + headers: { + loading: "no", + }, + }); +} + +// node 列表 all +export function getNodeListAll(params) { + return axios({ + url: "/node/list_data_all.json", + method: "get", + params, + }); +} + +// node group all +export function getNodeGroupAll() { + return axios({ + url: "/node/list_group_all.json", + method: "get", + }); +} + +// 节点和版本信息 +export function getNodeListWithVersion(params) { + return axios({ + url: "/node/list_data_with_version", + method: "get", + params: params, + }); +} + +// // node 状态 +// export function getNodeStatus(nodeId) { +// return axios({ +// url: "/node/node_status", +// method: "post", +// data: { nodeId }, +// }); +// } + +// 节点 + 项目列表 +export function getProjectList(params, loading) { + return axios({ + url: "/node/project_list", + method: "post", + data: params, + headers: { + loading: loading === false ? "no" : "", + }, + }); +} + +// 节点 + 项目列表 +export function getProjectListAll(params) { + return axios({ + url: "/node/project_list_all", + method: "get", + params, + }); +} + +// 同步节点项目 +export function syncProject(nodeId) { + return axios({ + url: "/node/sync_project", + method: "get", + params: { nodeId: nodeId }, + }); +} + +export function syncToWorkspace(params) { + return axios({ + url: "/node/sync-to-workspace", + method: "get", + params: params, + }); +} + +// +export function sortItem(params) { + return axios({ + url: "/node/sort-item", + method: "get", + params: params, + }); +} + +// 项目排序 +export function sortItemProject(params) { + return axios({ + url: "/node/project-sort-item", + method: "get", + params: params, + }); +} + +/** + * 编辑 node + * @param { + * id: ID, + * name: 节点名称, + * group: 分组名称, + * sshId: SSH ID, + * protocol: 协议 HTTPS || HTTP, + * url: URL 地址, + * timeOut: 超时时间, + * cycle: 监控周期, + * openStatus: 状态, + * loginName: 用户名, + * loginPwd: 密码, + * type: 操作类型 add || update + * } params + */ +export function editNode(params) { + const data = { + id: params.id, + name: params.name, + group: params.group, + sshId: params.sshId, + protocol: params.protocol, + url: params.url, + timeOut: params.timeOut, + cycle: params.cycle, + openStatus: params.openStatus, + loginName: params.loginName, + loginPwd: params.loginPwd, + type: params.type, + httpProxy: params.httpProxy, + httpProxyType: params.httpProxyType, + }; + return axios({ + url: "/node/save.json", + method: "post", + data, + }); +} + +// 删除 node +export function deleteNode(id) { + return axios({ + url: "/node/del.json", + method: "post", + data: { id }, + }); +} + +// 解绑 node +export function unbind(id) { + return axios({ + url: "/node/unbind.json", + method: "get", + params: { id }, + }); +} + +// // 节点 top 命令 +// export function getNodeTop(nodeId) { +// return axios({ +// url: "/node/getTop", +// method: "post", +// data: { nodeId }, +// headers: { +// loading: "no", +// }, +// }); +// } + +// 获取进程列表 +export function getProcessList(data) { + return axios({ + url: "/node/processList", + method: "post", + data: data, + timeout: 0, + headers: { + loading: "no", + tip: "no", + }, + }); +} + +/** + * 杀掉进程 + * @param {nodeId, pid} params + */ +export function killPid(params) { + return axios({ + url: "/node/kill.json", + method: "post", + data: params, + }); +} + +/** + * 节点监控图表数据 + * @param { + * nodeId: 节点 ID, + * time: 时间段,格式:yyyy-MM-dd HH:mm:ss ~ yyyy-MM-dd HH:mm:ss + * } params + */ +export function nodeMonitorData(params, loading) { + return axios({ + url: "/node/node_monitor_data.json", + method: "post", + data: params, + headers: { + loading: loading === false ? "no" : "", + }, + }); +} + +/** + * 上传升级文件 + * @param { + * file: 文件 multipart/form-data, + * nodeId: 节点 ID + * } formData + */ +export function uploadAgentFile(formData) { + return axios({ + url: "/node/upload-agent-sharding", + headers: { + "Content-Type": "multipart/form-data;charset=UTF-8", + loading: "no", + }, + method: "post", + // 0 表示无超时时间 + timeout: 0, + data: formData, + }); +} + +/** + * 上传文件合并 + * @returns json + */ +export function uploadAgentFileMerge(data) { + return axios({ + url: "/node/upload-agent-sharding-merge", + method: "post", + data: data, + // 0 表示无超时时间 + timeout: 0, + }); +} + +/** + * 检查远程最新 + * @returns json + */ +export function checkVersion() { + return axios({ + url: "/node/check_version.json", + method: "get", + data: {}, + }); +} + +/** + * 快速安装 + * @returns1 + */ +export function fastInstall() { + return axios({ + url: "/node/fast_install.json", + method: "get", + data: {}, + }); +} + +/** + * 拉取结果 + * @param {JSON} params + * @returns + */ +export function pullFastInstallResult(params) { + return axios({ + url: "/node/pull_fast_install_result.json", + method: "get", + params: params, + headers: { + loading: "no", + }, + }); +} + +/** + * 安装确认 + * @param {Json} params + * @returns + */ +export function confirmFastInstall(params) { + return axios({ + url: "/node/confirm_fast_install.json", + method: "get", + params: params, + }); +} + +/** + * 下载远程文件 + * @returns json + */ +export function downloadRemote() { + return axios({ + url: "/node/download_remote.json", + method: "get", + // 0 表示无超时时间 + timeout: 0, + data: {}, + }); +} diff --git a/web-vue/src/api/operation-log.js b/web-vue/src/api/operation-log.ts similarity index 100% rename from web-vue/src/api/operation-log.js rename to web-vue/src/api/operation-log.ts diff --git a/web-vue/src/api/repository.js b/web-vue/src/api/repository.ts similarity index 100% rename from web-vue/src/api/repository.js rename to web-vue/src/api/repository.ts diff --git a/web-vue/src/api/role.js b/web-vue/src/api/role.ts similarity index 100% rename from web-vue/src/api/role.js rename to web-vue/src/api/role.ts diff --git a/web-vue/src/api/server-script.js b/web-vue/src/api/server-script.ts similarity index 100% rename from web-vue/src/api/server-script.js rename to web-vue/src/api/server-script.ts diff --git a/web-vue/src/api/ssh-file.js b/web-vue/src/api/ssh-file.ts similarity index 84% rename from web-vue/src/api/ssh-file.js rename to web-vue/src/api/ssh-file.ts index bb2c34401..b3ed40747 100644 --- a/web-vue/src/api/ssh-file.js +++ b/web-vue/src/api/ssh-file.ts @@ -53,7 +53,7 @@ export function getFileList(baseUrl, params) { * @param {id, path, name} params */ export function downloadFile(baseUrl, params) { - return loadRouterBase(baseUrl + "download.html", params); + return loadRouterBase(baseUrl + "download", params); } /** @@ -120,15 +120,15 @@ export function renameFileFolder(baseUrl, params) { /** * 修改文件权限 - * @param {*} baseUrl + * @param {*} baseUrl * @param { * String id, * String allowPathParent, * String nextPath, * String fileName, * String permissionValue - * } params - * @returns + * } params + * @returns */ export function changeFilePermission(baseUrl, params) { return axios({ @@ -141,23 +141,23 @@ export function changeFilePermission(baseUrl, params) { /** * 权限字符串转权限对象 * @param {String} str "lrwxr-xr-x" - * @returns + * @returns */ export function parsePermissions(str) { - let permissions = {owner: {}, group: {}, others: {}}; + let permissions = { owner: {}, group: {}, others: {} }; - let chars = str.split(''); - permissions.owner.read = (chars[1] === "r"); - permissions.owner.write = (chars[2] === "w"); - permissions.owner.execute = (chars[3] === "x"); + let chars = str.split(""); + permissions.owner.read = chars[1] === "r"; + permissions.owner.write = chars[2] === "w"; + permissions.owner.execute = chars[3] === "x"; - permissions.group.read = (chars[4] === "r"); - permissions.group.write = (chars[5] === "w"); - permissions.group.execute = (chars[6] === "x"); + permissions.group.read = chars[4] === "r"; + permissions.group.write = chars[5] === "w"; + permissions.group.execute = chars[6] === "x"; - permissions.others.read = (chars[7] === "r"); - permissions.others.write = (chars[8] === "w"); - permissions.others.execute = (chars[9] === "x"); + permissions.others.read = chars[7] === "r"; + permissions.others.write = chars[8] === "w"; + permissions.others.execute = chars[9] === "x"; return permissions; } @@ -169,7 +169,7 @@ export function parsePermissions(str) { * group: { read: false, write: false, execute: false, }, * others: { read: false, write: false, execute: false, }, * } permissions - * @returns + * @returns */ export function calcFilePermissionValue(permissions) { let value = 0; diff --git a/web-vue/src/api/ssh.js b/web-vue/src/api/ssh.ts similarity index 100% rename from web-vue/src/api/ssh.js rename to web-vue/src/api/ssh.ts diff --git a/web-vue/src/api/system.ts b/web-vue/src/api/system.ts new file mode 100644 index 000000000..44e690a2d --- /dev/null +++ b/web-vue/src/api/system.ts @@ -0,0 +1,312 @@ +import axios from "./config"; +import { loadRouterBase } from "./config"; + +/** + * 日志列表 + * @param {nodeId} params + */ +export function getLogList(params) { + return axios({ + url: "/system/log_data.json", + method: "post", + data: params, + }); +} + +/** + * 下载日志 + * 下载文件的返回是 blob 类型,把 blob 用浏览器下载下来 + * @param {nodeId, path} params + */ +export function downloadFile(params) { + return loadRouterBase("/system/log_download", params); +} + +/** + * 删除日志 + * @param {nodeId, path} params + */ +export function deleteLog(params) { + return axios({ + url: "/system/log_del.json", + method: "post", + data: params, + }); +} + +/** + * server 缓存数据 + */ +export function getServerCache() { + return axios({ + url: "/system/server-cache", + method: "post", + }); +} + +/** + * 节点缓存数据 + * @param {String} nodeId + */ +export function getNodeCache(data) { + return axios({ + url: "/system/node_cache.json", + method: "post", + data, + }); +} + +/** + * 清空缓存 + * @param { + * type: 类型 + * nodeId: 节点 ID + * } params + */ +export function clearCache(params) { + return axios({ + url: "/system/clearCache.json", + method: "post", + data: params, + }); +} + +/** + * 清理错误工作空间的数据 + * + */ +export function clearErrorWorkspace(params) { + return axios({ + url: "/system/clear-error-workspace", + method: "get", + + headers: {}, + params, + }); +} + +/** + * 加载配置数据 + * @param {String} nodeId 节点 ID,若为空表示加载 Server 端配置 + */ +export function getConfigData(data) { + return axios({ + url: "/system/config-data", + method: "post", + data: data, + }); +} + +/** + * 加载ip配置数据 + */ +export function getIpConfigData() { + return axios({ + url: "/system/ip-config-data", + method: "post", + data: {}, + }); +} + +/** + * 编辑配置 + * @param {JSON} params { + * allowed: 允许访问,授权ip, + * prohibited: 禁止访问,禁止ip + * } + */ +export function editIpConfig(params) { + return axios({ + url: "/system/save_ip_config.json", + method: "post", + data: params, + }); +} + +/** + * 编辑配置 + * @param { + * nodeId: 节点 ID, + * content: 配置内容, + * restart: 是否重启 + * } params + */ +export function editConfig(params) { + return axios({ + url: "/system/save_config.json", + method: "post", + data: params, + }); +} + +/** + * 加载邮件配置 + */ +export function getMailConfigData() { + return axios({ + url: "/system/mail-config-data", + method: "post", + }); +} + +export function oauthConfigOauth2(params) { + return axios({ + url: "/system/oauth-config/oauth2", + method: "get", + params, + }); +} + +export function oauthConfigOauth2Save(params) { + return axios({ + url: "/system/oauth-config/oauth2-save", + method: "post", + data: params, + }); +} + +/** + * 编辑配置 + * @param { + * host: SMTP 服务器域名, + * port: SMTP 服务端口, + * user: 用户名, + * pass: 密码, + * from: 发送方,遵循RFC-822标准, + * sslEnable: 是否 SSL 安全连接, + * socketFactoryPort: SSL 加密端口 + * } params + */ +export function editMailConfig(params) { + return axios({ + url: "/system/mailConfig_save.json", + method: "post", + data: params, + }); +} + +/** + * 系统程序信息 + * @param {String} nodeId 节点 ID + */ +export function systemInfo(data) { + return axios({ + url: "/system/info", + method: "post", + headers: { + tip: "no", + loading: "no", + }, + data, + }); +} + +/** + * 上传升级文件 + * @param { + * file: 文件 multipart/form-data, + * nodeId: 节点 ID + * } formData + */ +export function uploadUpgradeFile(formData) { + return axios({ + url: "/system/upload-jar-sharding", + headers: { + "Content-Type": "multipart/form-data;charset=UTF-8", + loading: "no", + }, + method: "post", + // 0 表示无超时时间 + timeout: 0, + data: formData, + }); +} + +/** + * 上传文件合并 + *@param {String} nodeId 节点 ID + */ +export function uploadUpgradeFileMerge(data) { + return axios({ + url: "/system/upload-jar-sharding-merge", + method: "post", + headers: {}, + data: data, + // 0 表示无超时时间 + timeout: 0, + }); +} + +/** + * 获取更新日志 + *@param {String} nodeId 节点 ID + */ +export function changelog(data) { + return axios({ + url: "/system/change_log", + method: "post", + headers: {}, + data, + }); +} + +export function changBetaRelease(params) { + return axios({ + url: "/system/change-beta-release", + method: "get", + headers: {}, + params, + }); +} + +/** + * 检查新版本 + *@param {String} nodeId 节点 ID + */ +export function checkVersion(data) { + return axios({ + url: "/system/check_version.json", + method: "post", + headers: {}, + data, + }); +} + +/** + * 远程升级 + *@param {String} nodeId 节点 ID + */ +export function remoteUpgrade(params) { + return axios({ + url: "/system/remote_upgrade.json", + method: "get", + timeout: 0, + headers: {}, + params, + }); +} + +/** + * 加载 代理配置 + */ +export function getProxyConfig() { + return axios({ + url: "/system/get_proxy_config", + method: "get", + params: {}, + }); +} + +/** + * 保存代理配置 + */ +export function saveProxyConfig(data) { + return axios({ + url: "/system/save_proxy_config", + method: "post", + headers: { + "Content-Type": "application/json", + }, + data: data, + }); +} diff --git a/web-vue/src/api/system/assets-docker.js b/web-vue/src/api/system/assets-docker.ts similarity index 100% rename from web-vue/src/api/system/assets-docker.js rename to web-vue/src/api/system/assets-docker.ts diff --git a/web-vue3/src/api/system/assets-machine.ts b/web-vue/src/api/system/assets-machine.ts similarity index 60% rename from web-vue3/src/api/system/assets-machine.ts rename to web-vue/src/api/system/assets-machine.ts index fb126308d..a5e3fdbd1 100644 --- a/web-vue3/src/api/system/assets-machine.ts +++ b/web-vue/src/api/system/assets-machine.ts @@ -1,15 +1,15 @@ import axios from '@/api/config' // 机器 列表 -export function machineListData(params: any) { +export function machineListData(params) { return axios({ url: '/system/assets/machine/list-data', method: 'post', - params: params + data: params }) } -export function machineListGroup(params: any = {}) { +export function machineListGroup(params) { return axios({ url: '/system/assets/machine/list-group', method: 'get', @@ -18,29 +18,29 @@ export function machineListGroup(params: any = {}) { } // 编辑机器 -export function machineEdit(params: any) { +export function machineEdit(params) { return axios({ url: '/system/assets/machine/edit', method: 'post', - params: params + data: params }) } // 删除机器 -export function machineDelete(params: any) { +export function machineDelete(params) { return axios({ url: '/system/assets/machine/delete', method: 'post', - params: params + data: params }) } // 分配机器 -export function machineDistribute(params: any) { +export function machineDistribute(params) { return axios({ url: '/system/assets/machine/distribute', method: 'post', - params: params + data: params }) } @@ -52,7 +52,7 @@ export const statusMap = { } // 查看机器关联节点 -export function machineListNode(params: any) { +export function machineListNode(params) { return axios({ url: '/system/assets/machine/list-node', method: 'get', @@ -60,7 +60,7 @@ export function machineListNode(params: any) { }) } -export function machineListTemplateNode(params: any = {}) { +export function machineListTemplateNode(params) { return axios({ url: '/system/assets/machine/list-template-node', method: 'get', @@ -69,9 +69,9 @@ export function machineListTemplateNode(params: any = {}) { } /** - * 保存 白名单配置 + * 保存 授权配置 */ -export function saveWhitelist(data: any) { +export function saveWhitelist(data) { return axios({ url: '/system/assets/machine/save-whitelist', method: 'post', @@ -82,10 +82,26 @@ export function saveWhitelist(data: any) { /** * 保存 节点系统配置 */ -export function saveNodeConfig(data: any) { +export function saveNodeConfig(data) { return axios({ url: '/system/assets/machine/save-node-config', method: 'post', data: data }) } + +export function machineLonelyData(data) { + return axios({ + url: '/system/assets/machine/lonely-data', + method: 'get', + params: data + }) +} + +export function machineCorrectLonelyData(data) { + return axios({ + url: '/system/assets/machine/correct-lonely-data', + method: 'post', + data: data + }) +} diff --git a/web-vue/src/api/system/assets-ssh.js b/web-vue/src/api/system/assets-ssh.ts similarity index 100% rename from web-vue/src/api/system/assets-ssh.js rename to web-vue/src/api/system/assets-ssh.ts diff --git a/web-vue/src/api/system/cluster.js b/web-vue/src/api/system/cluster.ts similarity index 100% rename from web-vue/src/api/system/cluster.js rename to web-vue/src/api/system/cluster.ts diff --git a/web-vue/src/api/tools.js b/web-vue/src/api/tools.ts similarity index 100% rename from web-vue/src/api/tools.js rename to web-vue/src/api/tools.ts diff --git a/web-vue/src/api/tools/certificate.js b/web-vue/src/api/tools/certificate.ts similarity index 100% rename from web-vue/src/api/tools/certificate.js rename to web-vue/src/api/tools/certificate.ts diff --git a/web-vue/src/api/user/user-login-log.js b/web-vue/src/api/user/user-login-log.ts similarity index 100% rename from web-vue/src/api/user/user-login-log.js rename to web-vue/src/api/user/user-login-log.ts diff --git a/web-vue/src/api/user/user-permission.js b/web-vue/src/api/user/user-permission.ts similarity index 100% rename from web-vue/src/api/user/user-permission.js rename to web-vue/src/api/user/user-permission.ts diff --git a/web-vue3/src/api/user/user.ts b/web-vue/src/api/user/user.ts similarity index 75% rename from web-vue3/src/api/user/user.ts rename to web-vue/src/api/user/user.ts index 19b4417c4..c5a74f1d0 100644 --- a/web-vue3/src/api/user/user.ts +++ b/web-vue/src/api/user/user.ts @@ -1,8 +1,7 @@ -import { IResponse } from '@/interface/request' import axios from '../config' // login -export function login(params: any) { +export function login(params) { return axios({ url: '/userLogin', method: 'post', @@ -11,7 +10,7 @@ export function login(params: any) { } // oauth2Login -export function oauth2Login(params: any) { +export function oauth2Login(params) { return axios({ url: '/oauth2/login', method: 'post', @@ -19,7 +18,7 @@ export function oauth2Login(params: any) { }) } -export function oauth2Url(params: any) { +export function oauth2Url(params) { return axios({ url: '/oauth2-url', method: 'get', @@ -32,7 +31,7 @@ export function oauth2Url(params: any) { * @param {JSON} params * @returns */ -export function mfaVerify(params: any) { +export function mfaVerify(params) { return axios({ url: '/mfa_verify', method: 'get', @@ -49,7 +48,7 @@ export function refreshToken() { } // 关闭 两步验证信息 -export function closeMfa(params: any) { +export function closeMfa(params) { return axios({ url: '/user/close_mfa', method: 'get', @@ -70,7 +69,7 @@ export function generateMfa() { * @param {JSON} params * @returns */ -export function bindMfa(params: any) { +export function bindMfa(params) { return axios({ url: '/user/bind_mfa', method: 'get', @@ -80,14 +79,14 @@ export function bindMfa(params: any) { // 获取用户信息 export function getUserInfo() { - return axios>({ + return axios({ url: '/user/user-basic-info', method: 'post' }) } // 退出登录 -export function loginOut(params: any) { +export function loginOut(params) { return axios({ url: '/logout2', method: 'get', @@ -96,7 +95,7 @@ export function loginOut(params: any) { } // 修改密码 -export function updatePwd(params: any) { +export function updatePwd(params) { return axios({ url: '/user/updatePwd', method: 'post', @@ -113,7 +112,7 @@ export function getUserListAll() { } // 用户列表 -export function getUserList(params: any) { +export function getUserList(params) { return axios({ url: '/user/get_user_list', method: 'post', @@ -122,7 +121,7 @@ export function getUserList(params: any) { } // 编辑 -export function editUser(params: any) { +export function editUser(params) { return axios({ url: '/user/edit', method: 'post', @@ -131,7 +130,7 @@ export function editUser(params: any) { } // // 修改用户 -// export function updateUser(params:any) { +// export function updateUser(params) { // return axios({ // url: '/user/updateUser', // method: 'post', @@ -140,7 +139,7 @@ export function editUser(params: any) { // } // 删除用户 -export function deleteUser(id: string) { +export function deleteUser(id) { return axios({ url: '/user/deleteUser', method: 'post', @@ -158,7 +157,7 @@ export function deleteUser(id: string) { * workWx: 企业微信群通知地址 * } params */ -export function editUserInfo(params: any) { +export function editUserInfo(params) { return axios({ url: '/user/save_basicInfo.json', method: 'post', @@ -170,7 +169,7 @@ export function editUserInfo(params: any) { * 发送邮箱验证码 * @param {String} email 邮箱地址 */ -export function sendEmailCode(email: string) { +export function sendEmailCode(email) { return axios({ url: '/user/sendCode.json', method: 'post', @@ -184,7 +183,7 @@ export function sendEmailCode(email: string) { * @param {String} id 管理员 ID * @returns */ -export function unlockUser(id: string) { +export function unlockUser(id) { return axios({ url: '/user/unlock', method: 'get', @@ -197,7 +196,7 @@ export function unlockUser(id: string) { * @param {String} id 管理员 ID * @returns */ -export function closeUserMfa(id: string) { +export function closeUserMfa(id) { return axios({ url: '/user/close_user_mfa', method: 'get', @@ -210,7 +209,7 @@ export function closeUserMfa(id: string) { * @param {String} id 管理员 ID * @returns */ -export function restUserPwd(id: string) { +export function restUserPwd(id) { return axios({ url: '/user/rest-user-pwd', method: 'get', @@ -223,7 +222,7 @@ export function restUserPwd(id: string) { * @param {String} userId 管理员 ID * @returns */ -export function workspaceList(userId: string) { +export function workspaceList(userId) { return axios({ url: '/user/workspace_list', method: 'get', @@ -244,12 +243,33 @@ export function myWorkspace() { }) } +export function statWorkspace() { + return axios({ + url: '/stat/workspace', + method: 'get', + params: {} + }) +} + +/** + * 我的集群 + * + * @returns + */ +export function clusterList() { + return axios({ + url: '/user/cluster-list', + method: 'get', + params: {} + }) +} + /** * 保存我的工作空间 * * @returns */ -export function saveWorkspace(data: any) { +export function saveWorkspace(data) { return axios({ url: '/user/save-workspace', method: 'post', @@ -275,9 +295,10 @@ export function loginConfig() { /** * 登录验证码 + * * @returns */ -export function loginRandCode(params: object) { +export function loginRandCode(params) { return axios({ url: '/rand-code', method: 'get', @@ -285,7 +306,7 @@ export function loginRandCode(params: object) { }) } -export function listLoginLog(params: any) { +export function listLoginLog(params) { return axios({ url: '/user/list-login-log-data', method: 'post', @@ -293,10 +314,18 @@ export function listLoginLog(params: any) { }) } -export function listOperaterLog(params: any) { +export function listOperaterLog(params) { return axios({ url: '/user/list-operate-log-data', method: 'post', data: params }) } + +export function recentLogData(params) { + return axios({ + url: '/user/recent-log-data', + method: 'post', + data: params + }) +} diff --git a/web-vue/src/api/workspace.js b/web-vue/src/api/workspace.ts similarity index 100% rename from web-vue/src/api/workspace.js rename to web-vue/src/api/workspace.ts diff --git a/web-vue/src/assets/intro-custom-themes.css b/web-vue/src/assets/intro-custom-themes.css deleted file mode 100644 index c57fbb0f6..000000000 --- a/web-vue/src/assets/intro-custom-themes.css +++ /dev/null @@ -1,38 +0,0 @@ -.introjs-helperNumberLayer { - font-size: 14px; - text-shadow: none; - width: 22px; - height: 22px; - line-height: 22px; - border: 2px solid #ecf0f1; - border-radius: 50%; - background: #e74c3c; -} -.introjs-tooltip { - color: #333; - border-radius: 2px; -} -.introjs-button { - padding: 0.6em 0.8em; - text-shadow: none; - font-weight: bold; - color: #1890ff; - background: #ecf0f1; - background-image: none; - border-radius: 0.2em; - transition: background 0.3s, border 0.3s; -} -.introjs-prevbutton { - border-radius: 0.2em 0 0 0.2em; -} -.introjs-nextbutton { - border-radius: 0 0.2em 0.2em 0; -} -.introjs-button:hover, -.introjs-button:focus { - background: #1890ff; - color: #fff; - box-shadow: none; - border-color: #1890ff; - text-decoration: none; -} diff --git a/web-vue/src/assets/reset.less b/web-vue/src/assets/reset.less index 2d8cbb774..4864f0639 100644 --- a/web-vue/src/assets/reset.less +++ b/web-vue/src/assets/reset.less @@ -1,8 +1,7 @@ /* 全局样式,重置样式 */ html, body { - // 宽度 100vw 会出现横向滚动条 - // width: 100vw; + width: 100vw; height: 100vh; margin: 0; padding: 0; @@ -29,7 +28,7 @@ body { .filter { margin-bottom: 10px; } -/* +/* .ant-btn { margin-right: 10px; } */ @@ -71,9 +70,9 @@ pre { scrollbar-width: none; } -@color-border-last: rgba(140, 140, 140, 1); -@color-neutral-last: rgba(140, 140, 140, 0.2); -@scrollbar-size: 5px; +@color-border-last: #e5e5e5; +@color-neutral-last: #f7f8fa; +@scrollbar-size: 4px; // 兼容火狐 * { @@ -81,57 +80,24 @@ pre { scrollbar-color: @color-border-last @color-neutral-last; } // 滚动条样式 -::-webkit-scrollbar { +.ant-table-body::-webkit-scrollbar { width: @scrollbar-size; - height: @scrollbar-size; - border-radius: @scrollbar-size; + // height: @scrollbar-size; + // width: 10px; + height: 10px; + // width: 2px; + border-radius: 2px; background-color: transparent; } // 滚动条-活动按钮 -::-webkit-scrollbar-thumb { +.ant-table-body::-webkit-scrollbar-thumb { background: @color-border-last; - border-radius: @scrollbar-size; - box-shadow: outset 0 0 @scrollbar-size @color-border-last; + border-radius: 2px; + box-shadow: inset 0 0 6px @color-border-last; } // 滚动条背景 -::-webkit-scrollbar-track { +.ant-table-body::-webkit-scrollbar-track { background-color: @color-neutral-last; - border-radius: @scrollbar-size; - box-shadow: outset 0 0 @scrollbar-size @color-neutral-last; -} - -// 兼容火狐 -.force-scrollbar * { - scrollbar-width: thin; - scrollbar-color: @color-border-last @color-neutral-last; -} -// 滚动条样式 -.force-scrollbar *::-webkit-scrollbar { - width: @scrollbar-size !important; - height: @scrollbar-size !important; - border-radius: @scrollbar-size !important; - background-color: transparent !important; - display: block !important; -} -// 滚动条-活动按钮 -.force-scrollbar *::-webkit-scrollbar-thumb { - background: @color-border-last !important; - border-radius: @scrollbar-size !important; - box-shadow: outset 0 0 @scrollbar-size @color-border-last !important; -} -// 滚动条背景 -.force-scrollbar *::-webkit-scrollbar-track { - background-color: @color-neutral-last !important; - border-radius: @scrollbar-size !important; - box-shadow: outset 0 0 @scrollbar-size @color-neutral-last !important; -} - -.ant-space { - flex-wrap: wrap; - gap: 10px 0; -} -.text-overflow-hidden { - overflow: hidden; - text-overflow: ellipsis; - white-space: nowrap; + border-radius: 2px; + box-shadow: inset 0 0 6px @color-neutral-last; } diff --git a/web-vue3/src/components/Icon.ts b/web-vue/src/components/Icon.ts similarity index 91% rename from web-vue3/src/components/Icon.ts rename to web-vue/src/components/Icon.ts index 6fc317c0f..554a9524e 100644 --- a/web-vue3/src/components/Icon.ts +++ b/web-vue/src/components/Icon.ts @@ -15,7 +15,8 @@ import { ApartmentOutlined, DashboardOutlined, ProjectOutlined, - GatewayOutlined + GatewayOutlined, + LaptopOutlined } from '@ant-design/icons-vue' const iconObj = { @@ -34,7 +35,8 @@ const iconObj = { tool: ToolOutlined, dashboard: DashboardOutlined, project: ProjectOutlined, - gateway: GatewayOutlined + gateway: GatewayOutlined, + laptop: LaptopOutlined } const Icon = (props: { type: string }) => { diff --git a/web-vue/src/components/codeEditor/index.vue b/web-vue/src/components/codeEditor/index.vue index 06a14396a..6548ce534 100644 --- a/web-vue/src/components/codeEditor/index.vue +++ b/web-vue/src/components/codeEditor/index.vue @@ -1,184 +1,229 @@ + + diff --git a/web-vue/src/components/compositionTransfer/composition-transfer.vue b/web-vue/src/components/compositionTransfer/composition-transfer.vue index 6e11576dd..8414da086 100644 --- a/web-vue/src/components/compositionTransfer/composition-transfer.vue +++ b/web-vue/src/components/compositionTransfer/composition-transfer.vue @@ -1,6 +1,12 @@ + + diff --git a/web-vue/src/components/upgrade/index.vue b/web-vue/src/components/upgrade/index.vue index 3dbd71b9e..3da1ad1cd 100644 --- a/web-vue/src/components/upgrade/index.vue +++ b/web-vue/src/components/upgrade/index.vue @@ -14,29 +14,39 @@ beta计划: - + - 当前版本号:{{ temp.version }} - + + 当前版本号:{{ temp.version }} + + 已经运行时间:{{ formatDuration(temp.upTime) }} @@ -51,7 +61,11 @@ > - + @@ -67,48 +81,73 @@ - - - - 选择升级文件 + + + + 选择升级文件 - + - 上传升级包 + + 上传升级包 + - +
+ - diff --git a/web-vue3/src/d.ts/auto-global-import.d.ts b/web-vue/src/d.ts/auto-global-import.d.ts similarity index 77% rename from web-vue3/src/d.ts/auto-global-import.d.ts rename to web-vue/src/d.ts/auto-global-import.d.ts index b35af2197..630527da5 100644 --- a/web-vue3/src/d.ts/auto-global-import.d.ts +++ b/web-vue/src/d.ts/auto-global-import.d.ts @@ -1,6 +1,7 @@ /* eslint-disable */ /* prettier-ignore */ // @ts-nocheck +// noinspection JSUnusedGlobalSymbols // Generated by unplugin-auto-import export {} declare global { @@ -12,6 +13,9 @@ declare global { const $success: typeof import('./global/global')['$success'] const $warning: typeof import('./global/global')['$warning'] const appStore: typeof import('./global/global')['appStore'] + const guideStore: typeof import('./global/global')['guideStore'] const jpomWindow: typeof import('./global/global')['jpomWindow'] + const route: typeof import('./global/global')['route'] + const router: typeof import('./global/global')['router'] const userStore: typeof import('./global/global')['userStore'] } diff --git a/web-vue3/src/d.ts/auto-import.d.ts b/web-vue/src/d.ts/auto-import.d.ts similarity index 94% rename from web-vue3/src/d.ts/auto-import.d.ts rename to web-vue/src/d.ts/auto-import.d.ts index b9aa42b8e..0ce0ca81c 100644 --- a/web-vue3/src/d.ts/auto-import.d.ts +++ b/web-vue/src/d.ts/auto-import.d.ts @@ -1,6 +1,7 @@ /* eslint-disable */ /* prettier-ignore */ // @ts-nocheck +// noinspection JSUnusedGlobalSymbols // Generated by unplugin-auto-import export {} declare global { @@ -59,6 +60,7 @@ declare global { const toRaw: typeof import('vue')['toRaw'] const toRef: typeof import('vue')['toRef'] const toRefs: typeof import('vue')['toRefs'] + const toValue: typeof import('vue')['toValue'] const triggerRef: typeof import('vue')['triggerRef'] const unref: typeof import('vue')['unref'] const useAttrs: typeof import('vue')['useAttrs'] @@ -76,5 +78,6 @@ declare global { // for type re-export declare global { // @ts-ignore - export type { Component, ComponentPublicInstance, ComputedRef, InjectionKey, PropType, Ref, VNode } from 'vue' + export type { Component, ComponentPublicInstance, ComputedRef, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, VNode, WritableComputedRef } from 'vue' + import('vue') } diff --git a/web-vue3/src/d.ts/components.d.ts b/web-vue/src/d.ts/components.d.ts similarity index 57% rename from web-vue3/src/d.ts/components.d.ts rename to web-vue/src/d.ts/components.d.ts index c5dd97f7d..626e5deea 100644 --- a/web-vue3/src/d.ts/components.d.ts +++ b/web-vue/src/d.ts/components.d.ts @@ -3,11 +3,9 @@ // @ts-nocheck // Generated by unplugin-vue-components // Read more: https://github.com/vuejs/core/pull/3399 -import '@vue/runtime-core' - export {} -declare module '@vue/runtime-core' { +declare module 'vue' { export interface GlobalComponents { AAlert: typeof import('ant-design-vue/es')['Alert'] AAutoComplete: typeof import('ant-design-vue/es')['AutoComplete'] @@ -16,6 +14,8 @@ declare module '@vue/runtime-core' { AButtonGroup: typeof import('ant-design-vue/es')['ButtonGroup'] ACard: typeof import('ant-design-vue/es')['Card'] ACardMeta: typeof import('ant-design-vue/es')['CardMeta'] + ACascader: typeof import('ant-design-vue/es')['Cascader'] + ACheckbox: typeof import('ant-design-vue/es')['Checkbox'] ACol: typeof import('ant-design-vue/es')['Col'] ACollapse: typeof import('ant-design-vue/es')['Collapse'] ACollapsePanel: typeof import('ant-design-vue/es')['CollapsePanel'] @@ -26,9 +26,11 @@ declare module '@vue/runtime-core' { ADivider: typeof import('ant-design-vue/es')['Divider'] ADrawer: typeof import('ant-design-vue/es')['Drawer'] ADropdown: typeof import('ant-design-vue/es')['Dropdown'] + ADropdownButton: typeof import('ant-design-vue/es')['DropdownButton'] AEmpty: typeof import('ant-design-vue/es')['Empty'] AForm: typeof import('ant-design-vue/es')['Form'] AFormItem: typeof import('ant-design-vue/es')['FormItem'] + AFormItemRest: typeof import('ant-design-vue/es')['FormItemRest'] AInput: typeof import('ant-design-vue/es')['Input'] AInputGroup: typeof import('ant-design-vue/es')['InputGroup'] AInputNumber: typeof import('ant-design-vue/es')['InputNumber'] @@ -36,7 +38,6 @@ declare module '@vue/runtime-core' { AInputSearch: typeof import('ant-design-vue/es')['InputSearch'] ALayout: typeof import('ant-design-vue/es')['Layout'] ALayoutContent: typeof import('ant-design-vue/es')['LayoutContent'] - ALayoutHeader: typeof import('ant-design-vue/es')['LayoutHeader'] ALayoutSider: typeof import('ant-design-vue/es')['LayoutSider'] AList: typeof import('ant-design-vue/es')['List'] AListItem: typeof import('ant-design-vue/es')['ListItem'] @@ -45,20 +46,27 @@ declare module '@vue/runtime-core' { AMenuDivider: typeof import('ant-design-vue/es')['MenuDivider'] AMenuItem: typeof import('ant-design-vue/es')['MenuItem'] AModal: typeof import('ant-design-vue/es')['Modal'] + APageHeader: typeof import('ant-design-vue/es')['PageHeader'] APagination: typeof import('ant-design-vue/es')['Pagination'] + ApartmentOutlined: typeof import('@ant-design/icons-vue')['ApartmentOutlined'] ApiOutlined: typeof import('@ant-design/icons-vue')['ApiOutlined'] APopconfirm: typeof import('ant-design-vue/es')['Popconfirm'] APopover: typeof import('ant-design-vue/es')['Popover'] AProgress: typeof import('ant-design-vue/es')['Progress'] ARadio: typeof import('ant-design-vue/es')['Radio'] + ARadioButton: typeof import('ant-design-vue/es')['RadioButton'] ARadioGroup: typeof import('ant-design-vue/es')['RadioGroup'] ARangePicker: typeof import('ant-design-vue/es')['RangePicker'] + AreaChartOutlined: typeof import('@ant-design/icons-vue')['AreaChartOutlined'] AResult: typeof import('ant-design-vue/es')['Result'] ARow: typeof import('ant-design-vue/es')['Row'] + ArrowRightOutlined: typeof import('@ant-design/icons-vue')['ArrowRightOutlined'] ASelect: typeof import('ant-design-vue/es')['Select'] ASelectOptGroup: typeof import('ant-design-vue/es')['SelectOptGroup'] ASelectOption: typeof import('ant-design-vue/es')['SelectOption'] + ASkeleton: typeof import('ant-design-vue/es')['Skeleton'] ASpace: typeof import('ant-design-vue/es')['Space'] + ASpin: typeof import('ant-design-vue/es')['Spin'] AStatistic: typeof import('ant-design-vue/es')['Statistic'] AStatisticCountdown: typeof import('ant-design-vue/es')['StatisticCountdown'] AStep: typeof import('ant-design-vue/es')['Step'] @@ -77,39 +85,88 @@ declare module '@vue/runtime-core' { ATransfer: typeof import('ant-design-vue/es')['Transfer'] ATree: typeof import('ant-design-vue/es')['Tree'] ATypographyParagraph: typeof import('ant-design-vue/es')['TypographyParagraph'] - ATypographyText: typeof import('ant-design-vue/es')['TypographyText'] AUpload: typeof import('ant-design-vue/es')['Upload'] BarsOutlined: typeof import('@ant-design/icons-vue')['BarsOutlined'] + BlockOutlined: typeof import('@ant-design/icons-vue')['BlockOutlined'] + CheckCircleFilled: typeof import('@ant-design/icons-vue')['CheckCircleFilled'] CheckCircleOutlined: typeof import('@ant-design/icons-vue')['CheckCircleOutlined'] + CheckOutlined: typeof import('@ant-design/icons-vue')['CheckOutlined'] + CloseOutlined: typeof import('@ant-design/icons-vue')['CloseOutlined'] + CloudDownloadOutlined: typeof import('@ant-design/icons-vue')['CloudDownloadOutlined'] + CloudOutlined: typeof import('@ant-design/icons-vue')['CloudOutlined'] + CloudServerOutlined: typeof import('@ant-design/icons-vue')['CloudServerOutlined'] + ClusterOutlined: typeof import('@ant-design/icons-vue')['ClusterOutlined'] CodeEditor: typeof import('./../components/codeEditor/index.vue')['default'] - CodeEditor1: typeof import('./../components/codeEditor1/index.vue')['default'] + CodeOutlined: typeof import('@ant-design/icons-vue')['CodeOutlined'] CompositionTransfer: typeof import('./../components/compositionTransfer/composition-transfer.vue')['default'] - CopyOutlined: typeof import('@ant-design/icons-vue')['CopyOutlined'] + CustomInput: typeof import('./../components/customInput/index.vue')['default'] CustomSelect: typeof import('./../components/customSelect/index.vue')['default'] DeleteOutlined: typeof import('@ant-design/icons-vue')['DeleteOutlined'] + DesktopOutlined: typeof import('@ant-design/icons-vue')['DesktopOutlined'] DownloadOutlined: typeof import('@ant-design/icons-vue')['DownloadOutlined'] DownOutlined: typeof import('@ant-design/icons-vue')['DownOutlined'] + EditOutlined: typeof import('@ant-design/icons-vue')['EditOutlined'] + EllipsisOutlined: typeof import('@ant-design/icons-vue')['EllipsisOutlined'] + ExclamationCircleOutlined: typeof import('@ant-design/icons-vue')['ExclamationCircleOutlined'] + EyeInvisibleOutlined: typeof import('@ant-design/icons-vue')['EyeInvisibleOutlined'] + EyeOutlined: typeof import('@ant-design/icons-vue')['EyeOutlined'] + FileAddOutlined: typeof import('@ant-design/icons-vue')['FileAddOutlined'] + FileOutlined: typeof import('@ant-design/icons-vue')['FileOutlined'] + FileTextOutlined: typeof import('@ant-design/icons-vue')['FileTextOutlined'] + FileZipOutlined: typeof import('@ant-design/icons-vue')['FileZipOutlined'] + FolderAddOutlined: typeof import('@ant-design/icons-vue')['FolderAddOutlined'] + FullscreenOutlined: typeof import('@ant-design/icons-vue')['FullscreenOutlined'] + HighlightOutlined: typeof import('@ant-design/icons-vue')['HighlightOutlined'] + HomeOutlined: typeof import('@ant-design/icons-vue')['HomeOutlined'] + Index2: typeof import('./../components/logView/index2.vue')['default'] + InfoCircleOutlined: typeof import('@ant-design/icons-vue')['InfoCircleOutlined'] + InfoOutlined: typeof import('@ant-design/icons-vue')['InfoOutlined'] LayoutOutlined: typeof import('@ant-design/icons-vue')['LayoutOutlined'] + LeftOutlined: typeof import('@ant-design/icons-vue')['LeftOutlined'] LoadingOutlined: typeof import('@ant-design/icons-vue')['LoadingOutlined'] LockOutlined: typeof import('@ant-design/icons-vue')['LockOutlined'] + LoginOutlined: typeof import('@ant-design/icons-vue')['LoginOutlined'] LogoutOutlined: typeof import('@ant-design/icons-vue')['LogoutOutlined'] LogView: typeof import('./../components/logView/index.vue')['default'] + MenuOutlined: typeof import('@ant-design/icons-vue')['MenuOutlined'] + MessageOutlined: typeof import('@ant-design/icons-vue')['MessageOutlined'] + MinusCircleOutlined: typeof import('@ant-design/icons-vue')['MinusCircleOutlined'] + MoreOutlined: typeof import('@ant-design/icons-vue')['MoreOutlined'] + PlayCircleOutlined: typeof import('@ant-design/icons-vue')['PlayCircleOutlined'] + PlusOutlined: typeof import('@ant-design/icons-vue')['PlusOutlined'] + PlusSquareOutlined: typeof import('@ant-design/icons-vue')['PlusSquareOutlined'] ProfileOutlined: typeof import('@ant-design/icons-vue')['ProfileOutlined'] - QuestionCircleFilled: typeof import('@ant-design/icons-vue')['QuestionCircleFilled'] + PushpinOutlined: typeof import('@ant-design/icons-vue')['PushpinOutlined'] QuestionCircleOutlined: typeof import('@ant-design/icons-vue')['QuestionCircleOutlined'] + ReadOutlined: typeof import('@ant-design/icons-vue')['ReadOutlined'] + RedoOutlined: typeof import('@ant-design/icons-vue')['RedoOutlined'] + ReloadOutlined: typeof import('@ant-design/icons-vue')['ReloadOutlined'] + RestOutlined: typeof import('@ant-design/icons-vue')['RestOutlined'] + RetweetOutlined: typeof import('@ant-design/icons-vue')['RetweetOutlined'] + RocketOutlined: typeof import('@ant-design/icons-vue')['RocketOutlined'] RouterLink: typeof import('vue-router')['RouterLink'] RouterView: typeof import('vue-router')['RouterView'] + SelectOutlined: typeof import('@ant-design/icons-vue')['SelectOutlined'] SettingOutlined: typeof import('@ant-design/icons-vue')['SettingOutlined'] SkinOutlined: typeof import('@ant-design/icons-vue')['SkinOutlined'] SolutionOutlined: typeof import('@ant-design/icons-vue')['SolutionOutlined'] + SortAscendingOutlined: typeof import('@ant-design/icons-vue')['SortAscendingOutlined'] + SortDescendingOutlined: typeof import('@ant-design/icons-vue')['SortDescendingOutlined'] + StopFilled: typeof import('@ant-design/icons-vue')['StopFilled'] StopOutlined: typeof import('@ant-design/icons-vue')['StopOutlined'] SwapOutlined: typeof import('@ant-design/icons-vue')['SwapOutlined'] + SwitcherOutlined: typeof import('@ant-design/icons-vue')['SwitcherOutlined'] + SyncOutlined: typeof import('@ant-design/icons-vue')['SyncOutlined'] TableOutlined: typeof import('@ant-design/icons-vue')['TableOutlined'] + TagOutlined: typeof import('@ant-design/icons-vue')['TagOutlined'] + TagsOutlined: typeof import('@ant-design/icons-vue')['TagsOutlined'] Terminal: typeof import('./../components/terminal/index.vue')['default'] + UnorderedListOutlined: typeof import('@ant-design/icons-vue')['UnorderedListOutlined'] Upgrade: typeof import('./../components/upgrade/index.vue')['default'] UploadOutlined: typeof import('@ant-design/icons-vue')['UploadOutlined'] UserOutlined: typeof import('@ant-design/icons-vue')['UserOutlined'] ViewPre: typeof import('./../components/logView/view-pre.vue')['default'] WarningOutlined: typeof import('@ant-design/icons-vue')['WarningOutlined'] + WarningTwoTone: typeof import('@ant-design/icons-vue')['WarningTwoTone'] } } diff --git a/web-vue3/src/d.ts/global/global.ts b/web-vue/src/d.ts/global/global.ts similarity index 72% rename from web-vue3/src/d.ts/global/global.ts rename to web-vue/src/d.ts/global/global.ts index 44d628cfd..df987c0d8 100644 --- a/web-vue3/src/d.ts/global/global.ts +++ b/web-vue/src/d.ts/global/global.ts @@ -1,7 +1,8 @@ import { GlobalWindow } from '@/interface/common' -import { useAppStore } from '@/stores/app' import { message, notification, Modal } from 'ant-design-vue' +import { useAppStore } from '@/stores/app' import { useUserStore } from '@/stores/user' +import { useGuideStore } from '@/stores/guide' export const jpomWindow = window as unknown as GlobalWindow // 注册全局的组件 @@ -13,6 +14,8 @@ export const $info = Modal.info export const $error = Modal.error export const $warning = Modal.warning export const $success = Modal.success +// export const $route = useRoute() +// export const $router = useRouter() $notification.config({ top: '100px', @@ -28,3 +31,15 @@ export const appStore = () => { export const userStore = () => { return useUserStore() } + +export const guideStore = () => { + return useGuideStore() +} + +export const router = () => { + return useRouter() +} + +export const route = () => { + return useRoute() +} diff --git a/web-vue3/src/d.ts/vite-env.d.ts b/web-vue/src/d.ts/vite-env.d.ts similarity index 100% rename from web-vue3/src/d.ts/vite-env.d.ts rename to web-vue/src/d.ts/vite-env.d.ts diff --git a/web-vue3/src/interface/common.d.ts b/web-vue/src/interface/common.d.ts similarity index 100% rename from web-vue3/src/interface/common.d.ts rename to web-vue/src/interface/common.d.ts diff --git a/web-vue3/src/interface/global.d.ts b/web-vue/src/interface/global.d.ts similarity index 100% rename from web-vue3/src/interface/global.d.ts rename to web-vue/src/interface/global.d.ts diff --git a/web-vue3/src/interface/request.d.ts b/web-vue/src/interface/request.d.ts similarity index 100% rename from web-vue3/src/interface/request.d.ts rename to web-vue/src/interface/request.d.ts diff --git a/web-vue3/src/main.ts b/web-vue/src/main.ts similarity index 78% rename from web-vue3/src/main.ts rename to web-vue/src/main.ts index 57fdabb1a..f48ad8506 100644 --- a/web-vue3/src/main.ts +++ b/web-vue/src/main.ts @@ -1,9 +1,9 @@ // import '@/assets/style.css' -import '@/assets/reset.less' +// import '@/assets/reset.less' import App from './App.vue' import Antd from 'ant-design-vue' import router from './router' -import 'ant-design-vue/dist/antd.css' +// import 'ant-design-vue/dist/antd.css' import '@/router/auth' const pinia = createPinia() diff --git a/web-vue/src/pages/404/index.vue b/web-vue/src/pages/404/index.vue index 017336cff..ee7a20626 100644 --- a/web-vue/src/pages/404/index.vue +++ b/web-vue/src/pages/404/index.vue @@ -1,30 +1,33 @@ - \ No newline at end of file + diff --git a/web-vue/src/pages/build/details.vue b/web-vue/src/pages/build/details.vue index 4af7de961..b8836a996 100644 --- a/web-vue/src/pages/build/details.vue +++ b/web-vue/src/pages/build/details.vue @@ -5,7 +5,7 @@ {{ data.name }} - + @@ -16,11 +16,11 @@ {{ data.branchName }} {{ data.branchTagName }} @@ -31,20 +31,34 @@ - {{ statusMap[data.status] || "未知" }} + + {{ statusMap[data.status] || '未知' }} + + + {{ releaseMethodMap[data.releaseMethod] }} - {{ data.autoBuildCron }} - {{ data.aliasCode }} - - {{ data.sourceDirExist ? "存在" : "不存在" }} + + {{ data.autoBuildCron }} + + + {{ data.aliasCode }} + + + {{ data.sourceDirExist ? '存在' : '不存在' }} + + + {{ parseTime(data.createTimeMillis) }} - {{ parseTime(data.createTimeMillis) }} {{ parseTime(data.modifyTimeMillis) }} {{ data.modifyUser }} - {{ data.resultDirFile }} - {{ `${tempRepository ? tempRepository.name + "[" + tempRepository.gitUrl + "]" : ""}` }} + + {{ data.resultDirFile }} + + {{ + `${tempRepository ? tempRepository.name + '[' + tempRepository.gitUrl + ']' : ''}` + }} {{ data.repositoryLastCommitId }} @@ -52,43 +66,64 @@ 构建历史 - +
- #{{ item.buildNumberId }} + #{{ item.buildNumberId }} 构建备注:{{ item.buildRemark }}
- 状态:{{ statusMap[item.status] || "未知" }} + 状态:{{ statusMap[item.status] || '未知' }}
-
时间:{{ parseTime(item.startTime) }} ~ {{ parseTime(item.endTime) }}
-
触发类型:{{ triggerBuildTypeMap[item.triggerBuildType] || "未知" }}
-
占用空间:{{ renderSize(item.resultFileSize) }}(产物)/{{ renderSize(item.buildLogFileSize) }}(日志)
+
+ 时间:{{ parseTime(item.startTime) }} ~ + {{ parseTime(item.endTime) }} +
+
触发类型:{{ triggerBuildTypeMap[item.triggerBuildType] || '未知' }}
+
+ 占用空间:{{ renderSize(item.resultFileSize) }}(产物)/{{ renderSize(item.buildLogFileSize) }}(日志) +
-
构建耗时:{{ formatDuration((item.endTime || 0) - (item.startTime || 0), "", 2) }}
+
构建耗时:{{ formatDuration((item.endTime || 0) - (item.startTime || 0), '', 2) }}
发布方式: - {{ releaseMethodMap[item.releaseMethod] || "未知" }} + {{ releaseMethodMap[item.releaseMethod] || '未知' }}
操作: - 日志 + 日志 - - 产物 + + + + 产物 + @@ -99,10 +134,10 @@ @@ -138,19 +172,36 @@ - diff --git a/web-vue/src/pages/build/edit.vue b/web-vue/src/pages/build/edit.vue index 77b7c56b4..cef00b507 100644 --- a/web-vue/src/pages/build/edit.vue +++ b/web-vue/src/pages/build/edit.vue @@ -1,934 +1,1304 @@